feat: new Window Server helpers and app migrations

This commit is contained in:
2026-03-25 21:06:58 +01:00
parent 1ad9117fc1
commit afe0442d57
16 changed files with 654 additions and 778 deletions
+131
View File
@@ -0,0 +1,131 @@
/*
* standalone.hpp
* MontaukOS helpers for standalone Window Server apps
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <montauk/syscall.h>
#include "gui/canvas.hpp"
#include "gui/svg.hpp"
#include "gui/truetype.hpp"
namespace gui {
struct WsWindow {
int id;
uint32_t* pixels;
int width;
int height;
int scale_factor;
bool closed;
WsWindow()
: id(-1), pixels(nullptr), width(0), height(0), scale_factor(1), closed(false) {}
bool create(const char* title, int w, int h) {
Montauk::WinCreateResult wres;
if (montauk::win_create(title, w, h, &wres) < 0 || wres.id < 0)
return false;
id = wres.id;
pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
width = w;
height = h;
scale_factor = montauk::win_getscale();
closed = false;
return true;
}
int poll(Montauk::WinEvent* ev) {
if (id < 0 || closed) return -1;
int r = montauk::win_poll(id, ev);
if (r <= 0) return r;
if (ev->type == 2) {
width = ev->resize.w;
height = ev->resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(id, width, height);
} else if (ev->type == 3) {
closed = true;
} else if (ev->type == 4) {
scale_factor = ev->scale.scale;
}
return r;
}
Canvas canvas() const {
return Canvas(pixels, width, height);
}
void present() const {
if (id >= 0)
montauk::win_present(id);
}
void set_cursor(int cursor) const {
if (id >= 0)
montauk::win_setcursor(id, cursor);
}
void destroy() {
if (id >= 0)
montauk::win_destroy(id);
id = -1;
pixels = nullptr;
width = 0;
height = 0;
closed = true;
}
};
inline int text_width(TrueTypeFont* font, const char* text, int size) {
if (font && font->valid)
return font->measure_text(text, size);
int len = 0;
while (text && text[len]) len++;
return len * FONT_WIDTH;
}
inline int text_height(TrueTypeFont* font, int size) {
if (font && font->valid) {
GlyphCache* cache = font->get_cache(size);
if (cache)
return cache->ascent - cache->descent;
}
return size;
}
inline void draw_text(Canvas& c, TrueTypeFont* font, int x, int y,
const char* text, Color color, int size) {
if (!text || !text[0]) return;
if (font && font->valid)
font->draw_to_buffer(c.pixels, c.w, c.h, x, y, text, color, size);
}
inline void fill_circle(Canvas& c, int cx, int cy, int r, Color color) {
if (r <= 0) return;
for (int dy = -r; dy <= r; dy++) {
for (int dx = -r; dx <= r; dx++) {
if (dx * dx + dy * dy <= r * r)
c.put_pixel(cx + dx, cy + dy, color);
}
}
}
inline void draw_button(Canvas& c, TrueTypeFont* font, int x, int y, int w, int h,
const char* label, Color bg, Color fg, int radius,
int size = 16) {
c.fill_rounded_rect(x, y, w, h, radius, bg);
int tw = text_width(font, label, size);
int th = text_height(font, size);
draw_text(c, font, x + (w - tw) / 2, y + (h - th) / 2, label, fg, size);
}
inline void draw_icon(Canvas& c, int x, int y, const SvgIcon& icon) {
c.icon(x, y, icon);
}
} // namespace gui
+63 -145
View File
@@ -8,6 +8,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
extern "C" {
@@ -84,90 +85,6 @@ static int g_scroll = 0;
static char g_status[80];
static uint64_t g_status_time = 0;
// ============================================================================
// Pixel helpers
// ============================================================================
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w > bw ? bw : x + w;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static void px_fill_rounded(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, int r, Color c) {
uint32_t v = c.to_pixel();
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= bh) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= bw) continue;
bool skip = false;
int cx2, cy2;
if (col < r && row < r) { cx2 = r - col - 1; cy2 = r - row - 1; if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
else if (col >= w - r && row < r) { cx2 = col - (w - r); cy2 = r - row - 1; if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
else if (col < r && row >= h - r) { cx2 = r - col - 1; cy2 = row - (h - r); if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
else if (col >= w - r && row >= h - r) { cx2 = col - (w - r); cy2 = row - (h - r); if (cx2*cx2 + cy2*cy2 >= r*r) skip = true; }
if (!skip) px[dy * bw + dx] = v;
}
}
}
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c) {
if (y < 0 || y >= bh) return;
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x;
int x1 = x + w > bw ? bw : x + w;
for (int col = x0; col < x1; col++)
px[y * bw + col] = v;
}
static void px_circle(uint32_t* px, int bw, int bh,
int cx, int cy, int r, Color c) {
uint32_t v = c.to_pixel();
for (int dy = -r; dy <= r; dy++) {
int py = cy + dy;
if (py < 0 || py >= bh) continue;
for (int dx = -r; dx <= r; dx++) {
int ppx = cx + dx;
if (ppx < 0 || ppx >= bw) continue;
if (dx * dx + dy * dy <= r * r)
px[py * bw + ppx] = v;
}
}
}
static void px_text(uint32_t* px, int bw, int bh,
int x, int y, const char* text, Color c, int size = FONT_SIZE) {
if (g_font)
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
}
static int text_w(const char* text, int size = FONT_SIZE) {
return g_font ? g_font->measure_text(text, size) : 0;
}
static int font_h(int size = FONT_SIZE) {
if (!g_font) return 14;
auto* cache = g_font->get_cache(size);
return cache->ascent - cache->descent;
}
static void px_button(uint32_t* px, int bw, int bh,
int x, int y, int w, int h,
const char* label, Color bg, Color fg, int r = BTN_RAD) {
px_fill_rounded(px, bw, bh, x, y, w, h, r, bg);
int tw = text_w(label, FONT_SIZE_SM);
int fh = font_h(FONT_SIZE_SM);
px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2 - 1, label, fg, FONT_SIZE_SM);
}
// ============================================================================
// Bluetooth helpers
// ============================================================================
@@ -283,33 +200,33 @@ static const char* rssi_bar(int8_t rssi) {
// Render
// ============================================================================
static void render(uint32_t* pixels) {
static void render(Canvas& canvas) {
int W = g_win_w;
int H = g_win_h;
// Background
px_fill(pixels, W, H, 0, 0, W, H, BG_COLOR);
canvas.fill(BG_COLOR);
// Tab bar
int tab_y = 0;
px_fill(pixels, W, H, 0, tab_y, W, TAB_H, TAB_BG);
px_hline(pixels, W, H, 0, tab_y + TAB_H - 1, W, BORDER);
canvas.fill_rect(0, tab_y, W, TAB_H, TAB_BG);
canvas.hline(0, tab_y + TAB_H - 1, W, BORDER);
int tab_w = W / 2;
const char* tab_labels[] = { "Devices", "Scan" };
for (int i = 0; i < 2; i++) {
// Active tab gets white background to merge with content area
if (g_tab == i)
px_fill(pixels, W, H, i * tab_w, tab_y, tab_w, TAB_H, BG_COLOR);
canvas.fill_rect(i * tab_w, tab_y, tab_w, TAB_H, BG_COLOR);
Color tc = (g_tab == i) ? TAB_ACTIVE : TAB_INACTIVE;
int tw = text_w(tab_labels[i], FONT_SIZE);
int tw = text_width(g_font, tab_labels[i], FONT_SIZE);
int tx = i * tab_w + (tab_w - tw) / 2;
px_text(pixels, W, H, tx, tab_y + (TAB_H - font_h(FONT_SIZE)) / 2,
draw_text(canvas, g_font, tx, tab_y + (TAB_H - text_height(g_font, FONT_SIZE)) / 2,
tab_labels[i], tc, FONT_SIZE);
// Active indicator underline
if (g_tab == i) {
px_fill(pixels, W, H, i * tab_w + 4, tab_y + TAB_H - 3, tab_w - 8, 3, TAB_ACTIVE);
canvas.fill_rect(i * tab_w + 4, tab_y + TAB_H - 3, tab_w - 8, 3, TAB_ACTIVE);
}
}
@@ -323,8 +240,9 @@ static void render(uint32_t* pixels) {
// Connected devices list
if (g_device_count == 0) {
const char* msg = "No connected devices";
int mw = text_w(msg, FONT_SIZE);
px_text(pixels, W, H, (W - mw) / 2, content_y + list_h / 2 - font_h(FONT_SIZE) / 2,
int mw = text_width(g_font, msg, FONT_SIZE);
draw_text(canvas, g_font,
(W - mw) / 2, content_y + list_h / 2 - text_height(g_font, FONT_SIZE) / 2,
msg, DIM_TEXT, FONT_SIZE);
} else {
for (int i = 0; i < g_device_count; i++) {
@@ -333,32 +251,31 @@ static void render(uint32_t* pixels) {
// Hover highlight
if (g_hover_row == i)
px_fill(pixels, W, H, 0, ry, W, ROW_H, ROW_HOVER);
canvas.fill_rect(0, ry, W, ROW_H, ROW_HOVER);
// Connected indicator dot
Color dot_c = g_devices[i].connected ? GREEN : DIM_TEXT;
px_circle(pixels, W, H, PAD + 6, ry + ROW_H / 2, 5, dot_c);
fill_circle(canvas, PAD + 6, ry + ROW_H / 2, 5, dot_c);
// Device name
px_text(pixels, W, H, PAD + 20, ry + 8,
g_device_names[i], TEXT_COLOR, FONT_SIZE);
draw_text(canvas, g_font, PAD + 20, ry + 8, g_device_names[i], TEXT_COLOR, FONT_SIZE);
// Address below name
char addr_str[24];
format_addr(addr_str, sizeof(addr_str), g_devices[i].bdAddr);
px_text(pixels, W, H, PAD + 20, ry + 8 + font_h(FONT_SIZE) + 2,
draw_text(canvas, g_font, PAD + 20, ry + 8 + text_height(g_font, FONT_SIZE) + 2,
addr_str, DIM_TEXT, FONT_SIZE_SM);
// Disconnect button
if (g_devices[i].connected) {
int bx = W - PAD - BTN_W;
int by = ry + (ROW_H - BTN_H) / 2;
px_button(pixels, W, H, bx, by, BTN_W, BTN_H,
"Disconnect", RED, WHITE);
draw_button(canvas, g_font, bx, by, BTN_W, BTN_H,
"Disconnect", RED, WHITE, BTN_RAD, FONT_SIZE_SM);
}
// Divider
px_hline(pixels, W, H, PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
canvas.hline(PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
}
}
} else {
@@ -368,11 +285,11 @@ static void render(uint32_t* pixels) {
int scan_btn_w = 100;
int scan_btn_x = (W - scan_btn_w) / 2;
if (g_scanning) {
px_button(pixels, W, H, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
"Scanning...", BORDER, DIM_TEXT);
draw_button(canvas, g_font, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
"Scanning...", BORDER, DIM_TEXT, BTN_RAD, FONT_SIZE_SM);
} else {
px_button(pixels, W, H, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
"Scan", ACCENT, WHITE);
draw_button(canvas, g_font, scan_btn_x, scan_btn_y, scan_btn_w, BTN_H,
"Scan", ACCENT, WHITE, BTN_RAD, FONT_SIZE_SM);
}
int list_top = scan_btn_y + BTN_H + 12;
@@ -380,8 +297,9 @@ static void render(uint32_t* pixels) {
if (g_scan_count == 0 && !g_scanning) {
const char* msg = "Press Scan to find devices";
int mw = text_w(msg, FONT_SIZE);
px_text(pixels, W, H, (W - mw) / 2, list_top + scan_list_h / 2 - font_h(FONT_SIZE) / 2,
int mw = text_width(g_font, msg, FONT_SIZE);
draw_text(canvas, g_font,
(W - mw) / 2, list_top + scan_list_h / 2 - text_height(g_font, FONT_SIZE) / 2,
msg, DIM_TEXT, FONT_SIZE);
} else {
for (int i = 0; i < g_scan_count; i++) {
@@ -390,16 +308,15 @@ static void render(uint32_t* pixels) {
// Hover highlight
if (g_hover_row == i)
px_fill(pixels, W, H, 0, ry, W, ROW_H, ROW_HOVER);
canvas.fill_rect(0, ry, W, ROW_H, ROW_HOVER);
// Device type indicator
const char* type_str = device_class_str(g_scan[i].classOfDevice);
px_circle(pixels, W, H, PAD + 6, ry + ROW_H / 2, 5, ACCENT);
fill_circle(canvas, PAD + 6, ry + ROW_H / 2, 5, ACCENT);
// Device name (or address if unnamed)
const char* display_name = g_scan[i].name[0] ? g_scan[i].name : "Unknown Device";
px_text(pixels, W, H, PAD + 20, ry + 4,
display_name, TEXT_COLOR, FONT_SIZE);
draw_text(canvas, g_font, PAD + 20, ry + 4, display_name, TEXT_COLOR, FONT_SIZE);
// Type + signal below name
char detail[80];
@@ -407,7 +324,7 @@ static void render(uint32_t* pixels) {
format_addr(addr_str, sizeof(addr_str), g_scan[i].bdAddr);
snprintf(detail, sizeof(detail), "%s | %s | %s",
type_str, addr_str, rssi_bar(g_scan[i].rssi));
px_text(pixels, W, H, PAD + 20, ry + 4 + font_h(FONT_SIZE) + 2,
draw_text(canvas, g_font, PAD + 20, ry + 4 + text_height(g_font, FONT_SIZE) + 2,
detail, DIM_TEXT, FONT_SIZE_SM);
// Connect/Disconnect button
@@ -415,27 +332,27 @@ static void render(uint32_t* pixels) {
int bx = W - PAD - BTN_W;
int by = ry + (ROW_H - BTN_H) / 2;
if (conn) {
px_button(pixels, W, H, bx, by, BTN_W, BTN_H,
"Disconnect", RED, WHITE);
draw_button(canvas, g_font, bx, by, BTN_W, BTN_H,
"Disconnect", RED, WHITE, BTN_RAD, FONT_SIZE_SM);
} else {
px_button(pixels, W, H, bx, by, BTN_W, BTN_H,
"Connect", ACCENT, WHITE);
draw_button(canvas, g_font, bx, by, BTN_W, BTN_H,
"Connect", ACCENT, WHITE, BTN_RAD, FONT_SIZE_SM);
}
// Divider
px_hline(pixels, W, H, PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
canvas.hline(PAD, ry + ROW_H - 1, W - 2 * PAD, BORDER);
}
}
}
// Status bar
int status_y = H - STATUS_H;
px_fill(pixels, W, H, 0, status_y, W, STATUS_H, TAB_BG);
px_hline(pixels, W, H, 0, status_y, W, BORDER);
canvas.fill_rect(0, status_y, W, STATUS_H, TAB_BG);
canvas.hline(0, status_y, W, BORDER);
// Status bar content: adapter info on the left, status message on the right
uint64_t now = montauk::get_milliseconds();
int sy = status_y + (STATUS_H - font_h(FONT_SIZE_SM)) / 2;
int sy = status_y + (STATUS_H - text_height(g_font, FONT_SIZE_SM)) / 2;
if (g_adapter_ok) {
char adapter_info[96];
@@ -444,15 +361,15 @@ static void render(uint32_t* pixels) {
const char* name = g_adapter.name[0] ? g_adapter.name : "Adapter";
snprintf(adapter_info, sizeof(adapter_info), "%s | %s | %d connected",
name, addr_str, g_device_count);
px_text(pixels, W, H, PAD, sy, adapter_info, DIM_TEXT, FONT_SIZE_SM);
draw_text(canvas, g_font, PAD, sy, adapter_info, DIM_TEXT, FONT_SIZE_SM);
} else {
px_text(pixels, W, H, PAD, sy, "No adapter found", RED, FONT_SIZE_SM);
draw_text(canvas, g_font, PAD, sy, "No adapter found", RED, FONT_SIZE_SM);
}
// Temporary status message (right-aligned, fades after 5s)
if (g_status[0] && (now - g_status_time) < 5000) {
int sw = text_w(g_status, FONT_SIZE_SM);
px_text(pixels, W, H, W - PAD - sw, sy, g_status, DIM_TEXT, FONT_SIZE_SM);
int sw = text_width(g_font, g_status, FONT_SIZE_SM);
draw_text(canvas, g_font, W - PAD - sw, sy, g_status, DIM_TEXT, FONT_SIZE_SM);
}
}
@@ -569,34 +486,34 @@ extern "C" void _start() {
refresh_devices();
g_status[0] = 0;
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Bluetooth", WIN_W, WIN_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Bluetooth", WIN_W, WIN_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
Canvas canvas = win.canvas();
render(pixels);
montauk::win_present(win_id);
render(canvas);
win.present();
// Periodic refresh timer
uint64_t last_refresh = montauk::get_milliseconds();
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
// Handle pending scan (non-blocking: kick it off after render)
if (g_scanning) {
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
finish_scan();
refresh_devices();
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
continue;
}
@@ -607,8 +524,9 @@ extern "C" void _start() {
refresh_adapter();
refresh_devices();
last_refresh = now;
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
montauk::sleep_ms(16);
continue;
@@ -620,9 +538,8 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
redraw = true;
}
@@ -660,11 +577,12 @@ extern "C" void _start() {
}
if (redraw) {
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
}
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+12 -13
View File
@@ -5,6 +5,7 @@
*/
#include "devexplorer.h"
#include <gui/standalone.hpp>
// ============================================================================
// Global state definitions
@@ -203,16 +204,14 @@ extern "C" void _start() {
g_state.dev_count = montauk::devlist(g_state.devs, MAX_DEVS);
g_state.last_poll_ms = montauk::get_milliseconds();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Devices", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Devices", INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
uint32_t* pixels = win.pixels;
render(pixels);
montauk::win_present(win_id);
win.present();
while (true) {
Montauk::WinEvent ev;
@@ -249,11 +248,11 @@ extern "C" void _start() {
}
// Poll main window
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
if (r == 0) {
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
if (redraw_main) { render(pixels); win.present(); }
if (redraw_detail) { render_disk_detail(); montauk::win_present(g_state.detail.win_id); }
if (!redraw_main && !redraw_detail) montauk::sleep_ms(16);
continue;
@@ -263,9 +262,9 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
pixels = win.pixels;
redraw_main = true;
}
@@ -294,11 +293,11 @@ extern "C" void _start() {
}
}
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
if (redraw_main) { render(pixels); win.present(); }
if (redraw_detail) { render_disk_detail(); montauk::win_present(g_state.detail.win_id); }
}
close_disk_detail();
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+12 -13
View File
@@ -5,6 +5,7 @@
*/
#include "disks.h"
#include <gui/standalone.hpp>
// ============================================================================
// Global state definitions
@@ -341,16 +342,14 @@ extern "C" void _start() {
disktool_refresh();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Disks", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Disks", INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
uint32_t* pixels = win.pixels;
render(pixels);
montauk::win_present(win_id);
win.present();
while (true) {
Montauk::WinEvent ev;
@@ -400,12 +399,12 @@ extern "C" void _start() {
}
// Poll main window
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
if (r == 0) {
// Even with no main event, dialog may have triggered redraws
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
if (redraw_main) { render(pixels); win.present(); }
if (redraw_dlg) { render_format_window(); montauk::win_present(g_state.fmt_dlg.win_id); }
if (redraw_np) { render_newpart_window(); montauk::win_present(g_state.np_dlg.win_id); }
if (!redraw_main && !redraw_dlg && !redraw_np) montauk::sleep_ms(16);
@@ -416,9 +415,9 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
pixels = win.pixels;
redraw_main = true;
}
@@ -448,13 +447,13 @@ extern "C" void _start() {
}
}
if (redraw_main) { render(pixels); montauk::win_present(win_id); }
if (redraw_main) { render(pixels); win.present(); }
if (redraw_dlg) { render_format_window(); montauk::win_present(g_state.fmt_dlg.win_id); }
if (redraw_np) { render_newpart_window(); montauk::win_present(g_state.np_dlg.win_id); }
}
close_newpart_dialog();
close_format_dialog();
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+30 -49
View File
@@ -9,6 +9,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
using namespace gui;
@@ -75,21 +76,6 @@ static TrueTypeFont* g_ui_font = nullptr;
static stbtt_fontinfo g_preview_info;
static uint8_t* g_preview_data = nullptr;
// ============================================================================
// Helpers
// ============================================================================
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w > bw ? bw : x + w;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static const char* basename(const char* path) {
const char* last = path;
for (const char* p = path; *p; p++) {
@@ -248,14 +234,12 @@ static int calc_content_height() {
// Rendering
// ============================================================================
static void render(uint32_t* pixels) {
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
static void render(Canvas& canvas) {
canvas.fill(BG_COLOR);
if (!g_load_ok) {
if (g_ui_font) {
g_ui_font->draw_to_buffer(pixels, g_win_w, g_win_h,
PADDING, g_win_h / 2 - 8, "Error: could not load font", ERR_COLOR, 15);
}
draw_text(canvas, g_ui_font, PADDING, g_win_h / 2 - 8,
"Error: could not load font", ERR_COLOR, 15);
return;
}
@@ -279,27 +263,25 @@ static void render(uint32_t* pixels) {
char label[16] = {};
int_to_str(label, PREVIEW_SIZES[s]);
str_append(label, "px", 16);
g_ui_font->draw_to_buffer(pixels, g_win_w, g_win_h,
PADDING, y, label, LABEL_COLOR, UI_FONT_SZ);
draw_text(canvas, g_ui_font, PADDING, y, label, LABEL_COLOR, UI_FONT_SZ);
y += g_ui_font->get_line_height(UI_FONT_SZ) + 4;
}
draw_cached_text(pixels, g_win_w, g_win_h,
draw_cached_text(canvas.pixels, canvas.w, canvas.h,
PADDING, y, PANGRAM, TEXT_COLOR, s);
y += lh;
draw_cached_text(pixels, g_win_w, g_win_h,
draw_cached_text(canvas.pixels, canvas.w, canvas.h,
PADDING, y, UPPER, TEXT_COLOR, s);
y += lh;
draw_cached_text(pixels, g_win_w, g_win_h,
draw_cached_text(canvas.pixels, canvas.w, canvas.h,
PADDING, y, LOWER_NUM, TEXT_COLOR, s);
y += lh;
y += SECTION_GAP;
if (y >= 0 && y < g_win_h) {
px_fill(pixels, g_win_w, g_win_h,
PADDING, y, g_win_w - 2 * PADDING, 1, SEPARATOR);
canvas.fill_rect(PADDING, y, g_win_w - 2 * PADDING, 1, SEPARATOR);
}
y += 1 + SECTION_GAP;
}
@@ -312,8 +294,8 @@ static void render(uint32_t* pixels) {
int thumb_h = (view_h * view_h) / g_content_h;
if (thumb_h < 20) thumb_h = 20;
int thumb_y = (g_scroll_y * (view_h - thumb_h)) / max_scroll;
px_fill(pixels, g_win_w, g_win_h, sb_x, 0, 4, view_h, SCROLLBAR_BG);
px_fill(pixels, g_win_w, g_win_h, sb_x, thumb_y, 4, thumb_h, SCROLLBAR_FG);
canvas.fill_rect(sb_x, 0, 4, view_h, SCROLLBAR_BG);
canvas.fill_rect(sb_x, thumb_y, 4, thumb_h, SCROLLBAR_FG);
}
}
@@ -378,20 +360,17 @@ extern "C" void _start() {
g_content_h = calc_content_height();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create(title, INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
render(pixels);
montauk::win_present(win_id);
Canvas canvas = win.canvas();
render(canvas);
win.present();
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
@@ -403,12 +382,12 @@ extern "C" void _start() {
if (ev.type == 3) break;
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
continue;
}
@@ -427,8 +406,9 @@ extern "C" void _start() {
if (redraw) {
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
continue;
}
@@ -437,13 +417,14 @@ extern "C" void _start() {
if (ev.mouse.scroll != 0) {
g_scroll_y -= ev.mouse.scroll * SCROLL_STEP;
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
continue;
}
}
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+30 -89
View File
@@ -8,6 +8,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
extern "C" {
@@ -90,60 +91,6 @@ static int tb_fit_x0, tb_fit_x1;
static int tb_actual_x0, tb_actual_x1;
static int tb_open_x0, tb_open_x1;
// ============================================================================
// Helpers
// ============================================================================
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w > bw ? bw : x + w;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c) {
if (y < 0 || y >= bh) return;
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x;
int x1 = x + w > bw ? bw : x + w;
for (int col = x0; col < x1; col++)
px[y * bw + col] = v;
}
static void px_vline(uint32_t* px, int bw, int bh, int x, int y, int h, Color c) {
if (x < 0 || x >= bw) return;
uint32_t v = c.to_pixel();
int y0 = y < 0 ? 0 : y;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
px[row * bw + x] = v;
}
static void px_fill_rounded(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, int r, Color c) {
uint32_t v = c.to_pixel();
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= bh) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= bw) continue;
// Check corners
bool skip = false;
int cx, cy;
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
if (!skip) px[dy * bw + dx] = v;
}
}
}
static const char* basename(const char* path) {
const char* last = path;
for (const char* p = path; *p; p++) {
@@ -313,12 +260,12 @@ static void zoom_actual() {
// Rendering
// ============================================================================
static void render(uint32_t* pixels) {
static void render(Canvas& canvas) {
int vp_y0 = TOOLBAR_H;
int vp_y1 = g_win_h - STATUS_BAR_H;
// Fill viewport background
px_fill(pixels, g_win_w, g_win_h, 0, vp_y0, g_win_w, vp_y1 - vp_y0, BG_COLOR);
canvas.fill_rect(0, vp_y0, g_win_w, vp_y1 - vp_y0, BG_COLOR);
// Draw scaled image
if (g_image && g_load_ok) {
@@ -339,7 +286,7 @@ static void render(uint32_t* pixels) {
if (src_y >= g_img_h) src_y = g_img_h - 1;
const uint32_t* src_row = &g_image[src_y * g_img_w];
uint32_t* dst_row = &pixels[dy * g_win_w];
uint32_t* dst_row = &canvas.pixels[dy * g_win_w];
for (int dx = draw_x0; dx < draw_x1; dx++) {
int src_x = (int)((dx - g_pan_x) * inv_zoom);
@@ -376,30 +323,29 @@ static void render(uint32_t* pixels) {
}
}
} else if (!g_load_ok && g_font) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, vp_y0 + (vp_y1 - vp_y0) / 2 - 8, g_status, ERR_COLOR, 15);
draw_text(canvas, g_font, 20, vp_y0 + (vp_y1 - vp_y0) / 2 - 8, g_status, ERR_COLOR, 15);
}
// ---- Toolbar ----
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
px_hline(pixels, g_win_w, g_win_h, 0, TOOLBAR_H - 1, g_win_w, GRID_COLOR);
canvas.fill_rect(0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
canvas.hline(0, TOOLBAR_H - 1, g_win_w, GRID_COLOR);
int bx = 4;
auto tb_btn = [&](int w, bool active, const char* label, int& x0_out, int& x1_out) {
x0_out = bx;
x1_out = bx + w;
Color bg = active ? TB_BTN_ACTIVE : TB_BTN_BG;
px_fill_rounded(pixels, g_win_w, g_win_h, bx, TB_BTN_Y, w, TB_BTN_SIZE, TB_BTN_RAD, bg);
canvas.fill_rounded_rect(bx, TB_BTN_Y, w, TB_BTN_SIZE, TB_BTN_RAD, bg);
if (g_font && label[0]) {
int tw = g_font->measure_text(label, HEADER_FONT);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
draw_text(canvas, g_font,
bx + (w - tw) / 2, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2,
label, HEADER_TEXT, HEADER_FONT);
}
bx += w + 4;
};
auto tb_sep = [&]() {
px_vline(pixels, g_win_w, g_win_h, bx, 6, TOOLBAR_H - 12, TB_SEP_COLOR);
canvas.vline(bx, 6, TOOLBAR_H - 12, TB_SEP_COLOR);
bx += 8;
};
@@ -418,8 +364,7 @@ static void render(uint32_t* pixels) {
snprintf(zoom_label, 16, "%d%%", pct);
if (g_font) {
int tw = g_font->measure_text(zoom_label, HEADER_FONT);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
bx, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2,
draw_text(canvas, g_font, bx, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2,
zoom_label, HEADER_TEXT, HEADER_FONT);
bx += tw + 8;
}
@@ -433,18 +378,16 @@ static void render(uint32_t* pixels) {
// ---- Status bar ----
int sy = g_win_h - STATUS_BAR_H;
px_fill(pixels, g_win_w, g_win_h, 0, sy, g_win_w, STATUS_BAR_H, STATUS_BG);
canvas.fill_rect(0, sy, g_win_w, STATUS_BAR_H, STATUS_BG);
if (g_font) {
int sty = sy + (STATUS_BAR_H - HEADER_FONT) / 2;
// Left: filename and dimensions
if (g_load_ok && g_status[0]) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, 8, sty,
g_status, STATUS_TEXT, HEADER_FONT);
draw_text(canvas, g_font, 8, sty, g_status, STATUS_TEXT, HEADER_FONT);
} else if (!g_load_ok) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h, 8, sty,
"No image loaded", STATUS_TEXT, HEADER_FONT);
draw_text(canvas, g_font, 8, sty, "No image loaded", STATUS_TEXT, HEADER_FONT);
}
// Right: zoom level
@@ -453,8 +396,7 @@ static void render(uint32_t* pixels) {
int pct = (int)(g_zoom * 100 + 0.5f);
snprintf(right, 32, "%d%% ", pct);
int rw = g_font->measure_text(right, HEADER_FONT);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
g_win_w - rw - 8, sty, right, STATUS_TEXT, HEADER_FONT);
draw_text(canvas, g_font, g_win_w - rw - 8, sty, right, STATUS_TEXT, HEADER_FONT);
}
}
}
@@ -528,13 +470,11 @@ extern "C" void _start() {
}
}
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create(title, INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
Canvas canvas = win.canvas();
// Auto-fit on load if image is larger than viewport
if (g_load_ok) {
@@ -545,13 +485,13 @@ extern "C" void _start() {
}
}
render(pixels);
montauk::win_present(win_id);
render(canvas);
win.present();
// Event loop
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
if (r == 0) { montauk::sleep_ms(16); continue; }
@@ -560,12 +500,12 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
if (g_load_ok) clamp_pan();
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
continue;
}
@@ -646,12 +586,13 @@ extern "C" void _start() {
}
if (redraw) {
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
}
if (g_image) montauk::mfree(g_image);
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+15 -18
View File
@@ -5,6 +5,7 @@
*/
#include "installer.h"
#include <gui/standalone.hpp>
// ============================================================================
// Global state definitions
@@ -21,6 +22,7 @@ TrueTypeFont* g_font = nullptr;
uint32_t* g_pixels = nullptr;
uint32_t* g_backbuf = nullptr;
int g_win_id = -1;
static WsWindow g_window;
void apply_scale(int scale) {
switch (scale) {
@@ -56,10 +58,10 @@ void add_log(const char* msg) {
}
void flush_ui() {
if (g_pixels && g_backbuf && g_win_id >= 0) {
if (g_pixels && g_backbuf && g_window.id >= 0) {
render(g_backbuf);
montauk::memcpy(g_pixels, g_backbuf, g_win_w * g_win_h * 4);
montauk::win_present(g_win_id);
g_window.present();
}
}
@@ -273,20 +275,16 @@ extern "C" void _start() {
installer_refresh_disks();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Install MontaukOS", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
if (!g_window.create("Install MontaukOS", INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
g_pixels = pixels;
g_win_id = win_id;
g_pixels = g_window.pixels;
g_win_id = g_window.id;
g_backbuf = (uint32_t*)montauk::malloc(g_win_w * g_win_h * 4);
render(g_backbuf);
montauk::memcpy(pixels, g_backbuf, g_win_w * g_win_h * 4);
montauk::win_present(win_id);
montauk::memcpy(g_pixels, g_backbuf, g_win_w * g_win_h * 4);
g_window.present();
bool install_triggered = false;
@@ -294,7 +292,7 @@ extern "C" void _start() {
Montauk::WinEvent ev;
bool redraw = false;
int r = montauk::win_poll(win_id, &ev);
int r = g_window.poll(&ev);
if (r < 0) break;
if (r == 0) {
@@ -317,15 +315,14 @@ extern "C" void _start() {
if (ev.type == 3) break;
if (ev.type == 4) {
apply_scale(ev.scale.scale);
apply_scale(g_window.scale_factor);
redraw = true;
}
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_pixels = pixels;
g_win_w = g_window.width;
g_win_h = g_window.height;
g_pixels = g_window.pixels;
montauk::mfree(g_backbuf);
g_backbuf = (uint32_t*)montauk::malloc(g_win_w * g_win_h * 4);
redraw = true;
@@ -348,6 +345,6 @@ extern "C" void _start() {
if (redraw) flush_ui();
}
montauk::win_destroy(win_id);
g_window.destroy();
montauk::exit(0);
}
+12 -13
View File
@@ -5,6 +5,7 @@
*/
#include "paint.h"
#include <gui/standalone.hpp>
// ============================================================================
// Global state definitions
@@ -261,20 +262,18 @@ extern "C" void _start() {
// Push initial undo state
undo_push();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Paint", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Paint", INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
uint32_t* pixels = win.pixels;
render(pixels);
montauk::win_present(win_id);
win.present();
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
@@ -288,12 +287,12 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
pixels = win.pixels;
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
win.present();
continue;
}
@@ -477,10 +476,10 @@ extern "C" void _start() {
if (redraw) {
render(pixels);
montauk::win_present(win_id);
win.present();
}
}
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+12 -13
View File
@@ -5,6 +5,7 @@
*/
#include "pdfviewer.h"
#include <gui/standalone.hpp>
// ============================================================================
// Global state definitions
@@ -131,20 +132,18 @@ extern "C" void _start() {
snprintf(title, 64, "%s - PDF Viewer", fname);
}
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create(title, INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
uint32_t* pixels = win.pixels;
render(pixels);
montauk::win_present(win_id);
win.present();
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
@@ -158,12 +157,12 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
pixels = win.pixels;
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
win.present();
continue;
}
@@ -319,11 +318,11 @@ extern "C" void _start() {
if (redraw) {
render(pixels);
montauk::win_present(win_id);
win.present();
}
}
free_pdf();
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+13 -14
View File
@@ -5,6 +5,7 @@
*/
#include "spreadsheet.h"
#include <gui/standalone.hpp>
// ============================================================================
// Global state definitions
@@ -255,20 +256,18 @@ extern "C" void _start() {
snprintf(title, 64, "%s - Spreadsheet", fname);
}
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create(title, INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create(title, INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
uint32_t* pixels = win.pixels;
render(pixels);
montauk::win_present(win_id);
win.present();
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
@@ -282,12 +281,12 @@ extern "C" void _start() {
// Resize
if (ev.type == 2) {
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
g_win_w = win.width;
g_win_h = win.height;
pixels = win.pixels;
clamp_scroll();
render(pixels);
montauk::win_present(win_id);
win.present();
continue;
}
@@ -636,7 +635,7 @@ extern "C" void _start() {
}
}
}
montauk::win_setcursor(win_id, cursor);
win.set_cursor(cursor);
}
// Fill handle: drag in progress
@@ -783,10 +782,10 @@ extern "C" void _start() {
if (redraw) {
render(pixels);
montauk::win_present(win_id);
win.present();
}
}
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+77 -136
View File
@@ -8,6 +8,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
extern "C" {
@@ -27,21 +28,17 @@ static constexpr int FONT_SIZE = 16;
static constexpr int FONT_SIZE_LG = 28;
static constexpr int SLIDER_X = 24;
static constexpr int SLIDER_W = WIN_W - 48;
static constexpr int SLIDER_Y = 78;
static constexpr int SLIDER_H = 8;
static constexpr int KNOB_R = 10;
static constexpr int BTN_W = 48;
static constexpr int BTN_H = 28;
static constexpr int BTN_Y = 118;
static constexpr int BTN_RAD = 6;
static constexpr Color BG_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF);
static constexpr Color TEXT_COLOR = Color::from_rgb(0x22, 0x22, 0x22);
static constexpr Color DIM_TEXT = Color::from_rgb(0x66, 0x66, 0x66);
static constexpr Color ACCENT = Color::from_rgb(0x36, 0x7B, 0xF0);
static constexpr Color ACCENT_HOVER = Color::from_rgb(0x2A, 0x62, 0xC8);
static constexpr Color TRACK_BG = Color::from_rgb(0xDD, 0xDD, 0xDD);
static constexpr Color WHITE = Color::from_rgb(0xFF, 0xFF, 0xFF);
static constexpr Color MUTE_COLOR = Color::from_rgb(0xCC, 0x33, 0x33);
@@ -51,95 +48,13 @@ static constexpr Color MUTE_COLOR = Color::from_rgb(0xCC, 0x33, 0x33);
// ============================================================================
static TrueTypeFont* g_font = nullptr;
static int g_win_w = WIN_W;
static int g_win_h = WIN_H;
static int g_volume = 80;
static bool g_muted = false;
static int g_pre_mute_vol = 80;
static bool g_dragging = false;
// ============================================================================
// Pixel helpers (same pattern as disks app)
// ============================================================================
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w > bw ? bw : x + w;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c) {
if (y < 0 || y >= bh) return;
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x;
int x1 = x + w > bw ? bw : x + w;
for (int col = x0; col < x1; col++)
px[y * bw + col] = v;
}
static void px_fill_rounded(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, int r, Color c) {
uint32_t v = c.to_pixel();
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= bh) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= bw) continue;
bool skip = false;
int cx, cy;
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
if (!skip) px[dy * bw + dx] = v;
}
}
}
static void px_circle(uint32_t* px, int bw, int bh,
int cx, int cy, int r, Color c) {
uint32_t v = c.to_pixel();
for (int dy = -r; dy <= r; dy++) {
int py = cy + dy;
if (py < 0 || py >= bh) continue;
for (int dx = -r; dx <= r; dx++) {
int ppx = cx + dx;
if (ppx < 0 || ppx >= bw) continue;
if (dx * dx + dy * dy <= r * r)
px[py * bw + ppx] = v;
}
}
}
static void px_text(uint32_t* px, int bw, int bh,
int x, int y, const char* text, Color c, int size = FONT_SIZE) {
if (g_font)
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
}
static int text_w(const char* text, int size = FONT_SIZE) {
return g_font ? g_font->measure_text(text, size) : 0;
}
static int font_h(int size = FONT_SIZE) {
if (!g_font) return 16;
auto* cache = g_font->get_cache(size);
return cache->ascent - cache->descent;
}
static void px_button(uint32_t* px, int bw, int bh,
int x, int y, int w, int h,
const char* label, Color bg, Color fg, int r) {
px_fill_rounded(px, bw, bh, x, y, w, h, r, bg);
int tw = text_w(label);
int fh = font_h();
px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2, label, fg);
}
// ============================================================================
// Volume helpers
// ============================================================================
@@ -156,62 +71,80 @@ static void refresh_volume() {
if (v >= 0) g_volume = v;
}
// ============================================================================
// Layout helpers
// ============================================================================
static int slider_w() {
return g_win_w - 48;
}
static int button_y() {
return g_win_h - BTN_H - 18;
}
static void get_button_rects(Rect* minus_btn, Rect* plus_btn, Rect* mute_btn) {
int total_btn_w = BTN_W * 2 + 60 + 12 * 2;
int bx = (g_win_w - total_btn_w) / 2;
int by = button_y();
if (minus_btn) *minus_btn = {bx, by, BTN_W, BTN_H};
bx += BTN_W + 12;
if (plus_btn) *plus_btn = {bx, by, BTN_W, BTN_H};
bx += BTN_W + 12;
if (mute_btn) *mute_btn = {bx, by, 60, BTN_H};
}
// ============================================================================
// Render
// ============================================================================
static void render(uint32_t* pixels) {
int fh_lg = font_h(FONT_SIZE_LG);
static void render(Canvas& canvas) {
int fh_lg = text_height(g_font, FONT_SIZE_LG);
int slider_fill_w = slider_w();
// Background
px_fill(pixels, WIN_W, WIN_H, 0, 0, WIN_W, WIN_H, BG_COLOR);
canvas.fill(BG_COLOR);
// Volume percentage (large, centered)
char vol_str[8];
snprintf(vol_str, sizeof(vol_str), "%d%%", g_muted ? 0 : g_volume);
int vw = text_w(vol_str, FONT_SIZE_LG);
int vw = text_width(g_font, vol_str, FONT_SIZE_LG);
Color vol_color = g_muted ? MUTE_COLOR : ACCENT;
px_text(pixels, WIN_W, WIN_H, (WIN_W - vw) / 2, 20, vol_str, vol_color, FONT_SIZE_LG);
draw_text(canvas, g_font, (g_win_w - vw) / 2, 20, vol_str, vol_color, FONT_SIZE_LG);
// "Muted" label
if (g_muted) {
const char* muted_label = "Muted";
int mw = text_w(muted_label);
px_text(pixels, WIN_W, WIN_H, (WIN_W - mw) / 2,
20 + fh_lg + 4, muted_label, MUTE_COLOR);
int mw = text_width(g_font, muted_label, FONT_SIZE);
draw_text(canvas, g_font, (g_win_w - mw) / 2,
20 + fh_lg + 4, muted_label, MUTE_COLOR, FONT_SIZE);
}
// Slider track
px_fill_rounded(pixels, WIN_W, WIN_H, SLIDER_X, SLIDER_Y, SLIDER_W, SLIDER_H, 4, TRACK_BG);
canvas.fill_rounded_rect(SLIDER_X, SLIDER_Y, slider_fill_w, SLIDER_H, 4, TRACK_BG);
// Filled portion
int display_vol = g_muted ? 0 : g_volume;
int fill_w = (display_vol * SLIDER_W) / 100;
int fill_w = ((g_muted ? 0 : g_volume) * slider_fill_w) / 100;
if (fill_w > 0)
px_fill_rounded(pixels, WIN_W, WIN_H, SLIDER_X, SLIDER_Y, fill_w, SLIDER_H, 4, ACCENT);
canvas.fill_rounded_rect(SLIDER_X, SLIDER_Y, fill_w, SLIDER_H, 4, ACCENT);
// Knob
int knob_x = SLIDER_X + fill_w;
int knob_y = SLIDER_Y + SLIDER_H / 2;
px_circle(pixels, WIN_W, WIN_H, knob_x, knob_y, KNOB_R, ACCENT);
px_circle(pixels, WIN_W, WIN_H, knob_x, knob_y, KNOB_R - 3, WHITE);
fill_circle(canvas, knob_x, knob_y, KNOB_R, ACCENT);
fill_circle(canvas, knob_x, knob_y, KNOB_R - 3, WHITE);
// Buttons: [-] and [+] and [Mute]
int total_btn_w = BTN_W * 2 + 60 + 12 * 2; // minus, plus, mute + gaps
int bx = (WIN_W - total_btn_w) / 2;
px_button(pixels, WIN_W, WIN_H, bx, BTN_Y, BTN_W, BTN_H,
"-", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD);
bx += BTN_W + 12;
px_button(pixels, WIN_W, WIN_H, bx, BTN_Y, BTN_W, BTN_H,
"+", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD);
bx += BTN_W + 12;
Rect minus_btn, plus_btn, mute_btn;
get_button_rects(&minus_btn, &plus_btn, &mute_btn);
draw_button(canvas, g_font, minus_btn.x, minus_btn.y, minus_btn.w, minus_btn.h,
"-", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD, FONT_SIZE);
draw_button(canvas, g_font, plus_btn.x, plus_btn.y, plus_btn.w, plus_btn.h,
"+", Color::from_rgb(0xE0, 0xE0, 0xE0), TEXT_COLOR, BTN_RAD, FONT_SIZE);
Color mute_bg = g_muted ? MUTE_COLOR : Color::from_rgb(0xE0, 0xE0, 0xE0);
Color mute_fg = g_muted ? WHITE : TEXT_COLOR;
px_button(pixels, WIN_W, WIN_H, bx, BTN_Y, 60, BTN_H,
"Mute", mute_bg, mute_fg, BTN_RAD);
draw_button(canvas, g_font, mute_btn.x, mute_btn.y, mute_btn.w, mute_btn.h,
"Mute", mute_bg, mute_fg, BTN_RAD, FONT_SIZE);
}
// ============================================================================
@@ -219,7 +152,7 @@ static void render(uint32_t* pixels) {
// ============================================================================
static int vol_from_slider_x(int mx) {
int v = ((mx - SLIDER_X) * 100) / SLIDER_W;
int v = ((mx - SLIDER_X) * 100) / slider_w();
if (v < 0) v = 0;
if (v > 100) v = 100;
return v;
@@ -228,7 +161,7 @@ static int vol_from_slider_x(int mx) {
static bool handle_click(int mx, int my) {
// Slider area (generous vertical hit zone)
if (my >= SLIDER_Y - KNOB_R && my <= SLIDER_Y + SLIDER_H + KNOB_R &&
mx >= SLIDER_X - KNOB_R && mx <= SLIDER_X + SLIDER_W + KNOB_R) {
mx >= SLIDER_X - KNOB_R && mx <= SLIDER_X + slider_w() + KNOB_R) {
g_muted = false;
apply_volume(vol_from_slider_x(mx));
g_dragging = true;
@@ -236,28 +169,27 @@ static bool handle_click(int mx, int my) {
}
// Buttons
int total_btn_w = BTN_W * 2 + 60 + 12 * 2;
int bx = (WIN_W - total_btn_w) / 2;
Rect minus_btn, plus_btn, mute_btn;
get_button_rects(&minus_btn, &plus_btn, &mute_btn);
if (my >= BTN_Y && my < BTN_Y + BTN_H) {
int by = button_y();
if (my >= by && my < by + BTN_H) {
// [-] button
if (mx >= bx && mx < bx + BTN_W) {
if (minus_btn.contains(mx, my)) {
g_muted = false;
apply_volume(g_volume - 5);
return true;
}
bx += BTN_W + 12;
// [+] button
if (mx >= bx && mx < bx + BTN_W) {
if (plus_btn.contains(mx, my)) {
g_muted = false;
apply_volume(g_volume + 5);
return true;
}
bx += BTN_W + 12;
// [Mute] button
if (mx >= bx && mx < bx + 60) {
if (mute_btn.contains(mx, my)) {
if (g_muted) {
g_muted = false;
apply_volume(g_pre_mute_vol);
@@ -291,19 +223,22 @@ extern "C" void _start() {
refresh_volume();
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Volume", WIN_W, WIN_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Volume", WIN_W, WIN_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
g_win_w = win.width;
g_win_h = win.height;
render(pixels);
montauk::win_present(win_id);
{
Canvas canvas = win.canvas();
render(canvas);
}
win.present();
while (true) {
while (win.id >= 0 && !win.closed) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
if (r == 0) { montauk::sleep_ms(16); continue; }
@@ -311,6 +246,11 @@ extern "C" void _start() {
bool redraw = false;
if (ev.type == 3) break; // close
if (ev.type == 2) {
g_win_w = win.width;
g_win_h = win.height;
redraw = true;
}
// Keyboard
if (ev.type == 0 && ev.key.pressed) {
@@ -358,11 +298,12 @@ extern "C" void _start() {
}
if (redraw) {
render(pixels);
montauk::win_present(win_id);
Canvas canvas = win.canvas();
render(canvas);
win.present();
}
}
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+42 -147
View File
@@ -10,6 +10,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/svg.hpp>
#include <gui/truetype.hpp>
#include <tls/tls.hpp>
@@ -75,101 +76,6 @@ static bool g_tls_ready = false;
static uint32_t g_server_ip = 0;
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
// ============================================================================
// Pixel buffer helpers
// ============================================================================
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w > bw ? bw : x + w;
int y1 = y + h > bh ? bh : y + h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int len, Color c) {
if (y < 0 || y >= bh) return;
uint32_t v = c.to_pixel();
int x1 = x + len > bw ? bw : x + len;
for (int col = x < 0 ? 0 : x; col < x1; col++)
px[y * bw + col] = v;
}
// Integer square root (Newton's method).
static int isqrt(int n) {
if (n <= 0) return 0;
int x = n, y = (x + 1) / 2;
while (y < x) { x = y; y = (x + n / x) / 2; }
return x;
}
// Fill a rounded rectangle directly onto the pixel buffer.
static void px_fill_rounded(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, int r, Color c) {
if (r <= 0) { px_fill(px, bw, bh, x, y, w, h, c); return; }
if (r > w / 2) r = w / 2;
if (r > h / 2) r = h / 2;
uint32_t v = c.to_pixel();
// Corner arc centers
int cx_l = x + r;
int cx_r = x + w - 1 - r;
for (int row = y; row < y + h; row++) {
if (row < 0 || row >= bh) continue;
int left, right;
if (row < y + r) {
int dy = y + r - row; // 1..r
int dx = isqrt(r * r - dy * dy);
left = cx_l - dx;
right = cx_r + dx + 1;
} else if (row >= y + h - r) {
int dy = row - (y + h - 1 - r); // 1..r
int dx = isqrt(r * r - dy * dy);
left = cx_l - dx;
right = cx_r + dx + 1;
} else {
left = x;
right = x + w;
}
if (left < 0) left = 0;
if (right > bw) right = bw;
for (int col = left; col < right; col++)
px[row * bw + col] = v;
}
}
// Alpha-composite an SvgIcon onto the pixel buffer.
static void draw_icon(uint32_t* px, int bw, int bh,
int dx, int dy, const SvgIcon& icon) {
if (!icon.pixels) return;
for (int row = 0; row < icon.height; row++) {
int ty = dy + row;
if (ty < 0 || ty >= bh) continue;
for (int col = 0; col < icon.width; col++) {
int tx = dx + col;
if (tx < 0 || tx >= bw) continue;
uint32_t s = icon.pixels[row * icon.width + col];
uint8_t sa = (s >> 24) & 0xFF;
if (sa == 0) continue;
if (sa == 255) { px[ty * bw + tx] = s; continue; }
uint8_t sr = (s >> 16) & 0xFF, sg = (s >> 8) & 0xFF, sb = s & 0xFF;
uint32_t d = px[ty * bw + tx];
uint8_t dr = (d >> 16) & 0xFF, dg = (d >> 8) & 0xFF, db = d & 0xFF;
uint32_t inv = 255 - sa;
px[ty * bw + tx] = 0xFF000000u
| (((sr * sa + dr * inv) / 255u) << 16)
| (((sg * sa + dg * inv) / 255u) << 8)
| ((sb * sa + db * inv) / 255u);
}
}
}
// ============================================================================
// HTTP parsing
// ============================================================================
@@ -414,80 +320,62 @@ static constexpr Color WHITE_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF);
static constexpr int BTN_W = 110;
static constexpr int BTN_H = 28;
static constexpr int BTN_RADIUS = 6;
static constexpr int WEATHER_BTN_RADIUS = 6;
static void render(uint32_t* pixels) {
static void render(Canvas& canvas) {
// ── Background ───────────────────────────────────────────────────────────
px_fill(pixels, g_win_w, g_win_h, 0, 0,
g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
px_fill(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H,
g_win_w, FOOTER_H, FOOTER_BG);
canvas.fill_rect(0, 0, g_win_w, g_win_h - FOOTER_H, CONTENT_BG);
canvas.fill_rect(0, g_win_h - FOOTER_H, g_win_w, FOOTER_H, FOOTER_BG);
// Divider between content and location strip
px_hline(pixels, g_win_w, g_win_h, 0, HEADER_H, g_win_w, DIVIDER);
canvas.hline(0, HEADER_H, g_win_w, DIVIDER);
// Divider above footer
px_hline(pixels, g_win_w, g_win_h, 0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
canvas.hline(0, g_win_h - FOOTER_H, g_win_w, DIVIDER);
if (!g_font) return;
// ── Main content area (y=0..HEADER_H) ────────────────────────────────────
if (g_phase == AppPhase::LOADING) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, HEADER_H / 2 - 9,
draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
"Fetching weather data...", HINT_TEXT, 18);
} else if (g_phase == AppPhase::ERR) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, 20, g_status, ERR_TEXT, 15);
draw_text(canvas, g_font, 20, 20, g_status, ERR_TEXT, 15);
} else if (g_phase == AppPhase::IDLE) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, HEADER_H / 2 - 9,
draw_text(canvas, g_font, 20, HEADER_H / 2 - 9,
"Click Refresh to check weather.", HINT_TEXT, 18);
} else { // DONE
// Weather icon
draw_icon(pixels, g_win_w, g_win_h, ICON_X, ICON_Y, g_icon);
draw_icon(canvas, ICON_X, ICON_Y, g_icon);
// Temperature (large bold)
TrueTypeFont* temp_font = g_font_bold ? g_font_bold : g_font;
temp_font->draw_to_buffer(pixels, g_win_w, g_win_h,
INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
draw_text(canvas, temp_font, INFO_X, TEMP_Y, g_temp, DARK_TEXT, TEMP_SIZE);
// Weather description
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
draw_text(canvas, g_font, INFO_X, DESC_Y, g_desc, DARK_TEXT, DESC_SIZE);
// Feels like
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
draw_text(canvas, g_font, INFO_X, FEELS_Y, g_feels, MID_TEXT, LABEL_SIZE);
}
// ── Location strip (y=HEADER_H..g_win_h-FOOTER_H) ────────────────────────
if (g_phase == AppPhase::DONE) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
draw_text(canvas, g_font, 20, HEADER_H + 14, g_location, DARK_TEXT, LABEL_SIZE);
}
// ── Refresh button (rounded, in footer) ───────────────────────────────────
int btn_x = (g_win_w - BTN_W) / 2;
int btn_y = g_win_h - FOOTER_H + (FOOTER_H - BTN_H) / 2;
px_fill_rounded(pixels, g_win_w, g_win_h,
btn_x, btn_y, BTN_W, BTN_H, BTN_RADIUS, BTN_BG);
if (g_phase == AppPhase::LOADING) {
const char* lbl = "Loading...";
int sw = g_font->measure_text(lbl, 14);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 14) / 2,
lbl, WHITE_TEXT, 14);
draw_button(canvas, g_font, btn_x, btn_y, BTN_W, BTN_H,
"Loading...", BTN_BG, WHITE_TEXT, WEATHER_BTN_RADIUS, 14);
} else {
const char* lbl = "Refresh";
int sw = g_font->measure_text(lbl, 15);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
btn_x + (BTN_W - sw) / 2, btn_y + (BTN_H - 15) / 2,
lbl, WHITE_TEXT, 15);
draw_button(canvas, g_font, btn_x, btn_y, BTN_W, BTN_H,
"Refresh", BTN_BG, WHITE_TEXT, WEATHER_BTN_RADIUS, 15);
}
}
@@ -514,38 +402,43 @@ extern "C" void _start() {
apply_scale(montauk::win_getscale());
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Weather", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Weather", INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
Canvas canvas = win.canvas();
// Initial fetch on startup
g_phase = AppPhase::LOADING;
render(pixels);
montauk::win_present(win_id);
render(canvas);
win.present();
do_fetch();
// Event loop
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break;
if (r == 0) {
montauk::sleep_ms(16);
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
continue;
}
if (ev.type == 3) break; // close
if (ev.type == 4) {
apply_scale(ev.scale.scale);
apply_scale(win.scale_factor);
}
if (ev.type == 2) {
g_win_w = win.width;
g_win_h = win.height;
canvas = win.canvas();
}
if (ev.type == 1) {
@@ -558,18 +451,20 @@ extern "C" void _start() {
if (mx >= btn_x && mx < btn_x + BTN_W &&
my >= btn_y && my < btn_y + BTN_H) {
g_phase = AppPhase::LOADING;
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
do_fetch();
}
}
}
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
if (g_icon.pixels) svg_free(g_icon);
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+38 -89
View File
@@ -9,6 +9,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/standalone.hpp>
#include <gui/truetype.hpp>
#include <tls/tls.hpp>
@@ -81,48 +82,6 @@ static bool g_tls_ready = false;
static uint32_t g_server_ip = 0;
static tls::TrustAnchors g_tas = {nullptr, 0, 0};
// ============================================================================
// Pixel buffer helpers
// ============================================================================
static void px_fill(uint32_t* px, int bw, int x, int y, int w, int h, Color c) {
uint32_t v = c.to_pixel();
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
int x1 = x + w, y1 = y + h;
if (x1 > bw) x1 = bw;
if (y1 > g_win_h) y1 = g_win_h;
for (int row = y0; row < y1; row++)
for (int col = x0; col < x1; col++)
px[row * bw + col] = v;
}
static void px_hline(uint32_t* px, int bw, int x, int y, int len, Color c) {
if (y < 0 || y >= g_win_h) return;
uint32_t v = c.to_pixel();
int x1 = x + len;
if (x < 0) x = 0;
if (x1 > bw) x1 = bw;
for (int col = x; col < x1; col++)
px[y * bw + col] = v;
}
static void px_vline(uint32_t* px, int bw, int x, int y, int len, Color c) {
if (x < 0 || x >= bw) return;
uint32_t v = c.to_pixel();
int y1 = y + len;
if (y < 0) y = 0;
if (y1 > g_win_h) y1 = g_win_h;
for (int row = y; row < y1; row++)
px[row * bw + x] = v;
}
static void px_rect_outline(uint32_t* px, int bw, int x, int y, int w, int h, Color c) {
px_hline(px, bw, x, y, w, c);
px_hline(px, bw, x, y + h - 1, w, c);
px_vline(px, bw, x, y, h, c);
px_vline(px, bw, x + w - 1, y, h, c);
}
// ============================================================================
// UI scale
// ============================================================================
@@ -459,16 +418,16 @@ static void do_search(const char* query) {
// Rendering
// ============================================================================
static void render(uint32_t* pixels) {
static void render(Canvas& canvas) {
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
static constexpr Color HINT_COLOR = Color::from_rgb(0x99, 0x99, 0x99);
// Background
px_fill(pixels, g_win_w, 0, 0, g_win_w, g_win_h, WINDOW_BG);
canvas.fill(WINDOW_BG);
// ---- Toolbar ----
px_fill(pixels, g_win_w, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
px_hline(pixels, g_win_w, 0, TOOLBAR_H, g_win_w, BORDER);
canvas.fill_rect(0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
canvas.hline(0, TOOLBAR_H, g_win_w, BORDER);
// Search box geometry
int sb_y = 8, sb_h = TOOLBAR_H - 16;
@@ -477,26 +436,25 @@ static void render(uint32_t* pixels) {
int sb_w = g_win_w - sb_x - btn_gap - btn_w - 8;
if (sb_w < 80) sb_w = 80;
px_fill(pixels, g_win_w, sb_x, sb_y, sb_w, sb_h, WHITE);
px_rect_outline(pixels, g_win_w, sb_x, sb_y, sb_w, sb_h, BORDER);
canvas.fill_rect(sb_x, sb_y, sb_w, sb_h, WHITE);
canvas.rect(sb_x, sb_y, sb_w, sb_h, BORDER);
// Search box text + cursor
if (g_font) {
int ty = sb_y + (sb_h - FONT_SIZE) / 2;
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
sb_x + 6, ty, g_query, TEXT_COLOR, FONT_SIZE);
draw_text(canvas, g_font, sb_x + 6, ty, g_query, TEXT_COLOR, FONT_SIZE);
int qw = g_font->measure_text(g_query, FONT_SIZE);
int cx = sb_x + 6 + qw + 1;
if (cx < sb_x + sb_w - 4)
px_vline(pixels, g_win_w, cx, ty + 1, FONT_SIZE - 2, TEXT_COLOR);
canvas.vline(cx, ty + 1, FONT_SIZE - 2, TEXT_COLOR);
}
// Search button
int btn_x = sb_x + sb_w + btn_gap;
px_fill(pixels, g_win_w, btn_x, sb_y, btn_w, sb_h, ACCENT);
canvas.fill_rect(btn_x, sb_y, btn_w, sb_h, ACCENT);
if (g_font) {
int stw = g_font->measure_text("Search", FONT_SIZE);
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
draw_text(canvas, g_font,
btn_x + (btn_w - stw) / 2,
sb_y + (sb_h - FONT_SIZE) / 2,
"Search", WHITE, FONT_SIZE);
@@ -508,17 +466,14 @@ static void render(uint32_t* pixels) {
if (!g_font) return;
if (g_phase == AppPhase::IDLE) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
TEXT_PAD, cy + 16,
draw_text(canvas, g_font, TEXT_PAD, cy + 16,
"Type a topic and press Enter or click Search.",
HINT_COLOR, FONT_SIZE);
} else if (g_phase == AppPhase::LOADING) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
TEXT_PAD, cy + 16,
draw_text(canvas, g_font, TEXT_PAD, cy + 16,
"Searching Wikipedia...", HINT_COLOR, FONT_SIZE);
} else if (g_phase == AppPhase::ERR) {
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
TEXT_PAD, cy + 16, g_status, CLOSE_BTN, FONT_SIZE);
draw_text(canvas, g_font, TEXT_PAD, cy + 16, g_status, CLOSE_BTN, FONT_SIZE);
} else if (g_phase == AppPhase::DONE && g_line_count > 0) {
int visible = ch / g_line_h; // approximate using body line height
int y = cy + 8;
@@ -528,7 +483,7 @@ static void render(uint32_t* pixels) {
int lh = g_font->get_line_height(l.font_size) + 4;
if (y + lh > g_win_h) break;
if (l.text[0] != '\0') {
l.font->draw_to_buffer(pixels, g_win_w, g_win_h,
l.font->draw_to_buffer(canvas.pixels, canvas.w, canvas.h,
TEXT_PAD, y, l.text, l.color, l.font_size);
}
y += lh;
@@ -537,13 +492,12 @@ static void render(uint32_t* pixels) {
// Scrollbar
if (g_line_count > visible) {
int sbx = g_win_w - SCROLLBAR_W;
px_fill(pixels, g_win_w, sbx, cy, SCROLLBAR_W, ch, SCROLLBAR_BG);
canvas.fill_rect(sbx, cy, SCROLLBAR_W, ch, SCROLLBAR_BG);
int max_sc = g_line_count - visible;
int thumb_h = (visible * ch) / g_line_count;
if (thumb_h < 20) thumb_h = 20;
int thumb_y = cy + (g_scroll_y * (ch - thumb_h)) / (max_sc > 0 ? max_sc : 1);
px_fill(pixels, g_win_w, sbx + 2, thumb_y,
SCROLLBAR_W - 4, thumb_h, SCROLLBAR_FG);
canvas.fill_rect(sbx + 2, thumb_y, SCROLLBAR_W - 4, thumb_h, SCROLLBAR_FG);
}
}
}
@@ -576,30 +530,28 @@ extern "C" void _start() {
apply_scale(montauk::win_getscale());
// Create window
Montauk::WinCreateResult wres;
if (montauk::win_create("Wikipedia", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
WsWindow win;
if (!win.create("Wikipedia", INIT_W, INIT_H))
montauk::exit(1);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
render(pixels);
montauk::win_present(win_id);
Canvas canvas = win.canvas();
render(canvas);
win.present();
bool search_pending = false;
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
int r = win.poll(&ev);
if (r < 0) break; // window closed / error
if (r == 0) {
// No event — idle at ~60 fps
montauk::sleep_ms(16);
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
continue;
}
@@ -607,26 +559,21 @@ extern "C" void _start() {
if (ev.type == 3) break; // close
if (ev.type == 4) {
apply_scale(ev.scale.scale);
apply_scale(win.scale_factor);
if (g_phase == AppPhase::DONE && g_line_count > 0) {
build_display_lines(g_title, g_extract_buf, g_extract_len);
}
}
if (ev.type == 2) {
int new_w = ev.resize.w;
int new_h = ev.resize.h;
if (new_w > 0 && new_h > 0 && (new_w != g_win_w || new_h != g_win_h)) {
uint64_t new_va = montauk::win_resize(win_id, new_w, new_h);
if (new_va != 0) {
pixels = (uint32_t*)(uintptr_t)new_va;
g_win_w = new_w;
g_win_h = new_h;
if (win.width > 0 && win.height > 0) {
g_win_w = win.width;
g_win_h = win.height;
canvas = win.canvas();
if (g_phase == AppPhase::DONE && g_line_count > 0) {
build_display_lines(g_title, g_extract_buf, g_extract_len);
}
}
}
} else if (ev.type == 0 && ev.key.pressed) {
uint8_t ascii = ev.key.ascii;
@@ -684,15 +631,17 @@ extern "C" void _start() {
if (search_pending && g_query[0] != '\0') {
search_pending = false;
g_phase = AppPhase::LOADING;
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
do_search(g_query); // blocking
}
render(pixels);
montauk::win_present(win_id);
canvas = win.canvas();
render(canvas);
win.present();
}
montauk::win_destroy(win_id);
win.destroy();
montauk::exit(0);
}
+14 -18
View File
@@ -147,27 +147,23 @@ Events are delivered via `win_poll()` into a `WinEvent` struct:
### Drawing Helpers
Standalone apps typically define local pixel-drawing helpers since they work with raw `uint32_t*` buffers:
For new standalone apps, prefer the shared helpers in `#include <gui/standalone.hpp>`. It provides:
- `gui::WsWindow` for `win_create()` / `win_poll()` / `win_resize()` / `win_present()`
- `gui::Canvas` for drawing into the window buffer
- immediate-mode helpers like `draw_text()`, `draw_button()`, and `fill_circle()`
Older apps in the tree still define local `px_*` helpers, but new code should not need to.
```cpp
static void px_fill(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, uint32_t color) {
for (int row = y; row < y + h && row < bh; row++)
for (int col = x; col < x + w && col < bw; col++)
px[row * bw + col] = color;
}
gui::WsWindow win;
win.create("My App", 400, 300);
static void px_hline(uint32_t* px, int bw, int bh,
int x, int y, int w, uint32_t color) {
for (int col = x; col < x + w && col < bw; col++)
if (y >= 0 && y < bh) px[y * bw + col] = color;
}
static void px_text(uint32_t* px, int bw, int bh,
int x, int y, const char* text, Color c, int size) {
if (g_font)
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
}
gui::Canvas c = win.canvas();
c.fill(gui::colors::WHITE);
draw_text(c, g_font, 20, 30, "Hello", gui::colors::TEXT_COLOR, 18);
draw_button(c, g_font, 20, 60, 96, 28, "OK",
gui::colors::ACCENT, gui::colors::WHITE, 6, 16);
```
### Rounded Rectangles
+2 -1
View File
@@ -207,11 +207,12 @@ struct WinCreateResult {
**`WinEvent` struct:**
```cpp
struct WinEvent {
int type; // 0=Key, 1=Mouse, 2=Resize, 3=Close
int type; // 0=Key, 1=Mouse, 2=Resize, 3=Close, 4=Scale
union {
struct { uint8_t scancode; char ascii; bool pressed, shift, ctrl, alt; } key;
struct { int x, y; uint8_t buttons, prev_buttons; int32_t scroll; } mouse;
struct { int w, h; } resize;
struct { int scale; } scale;
};
};
```
+131
View File
@@ -0,0 +1,131 @@
/*
* standalone.hpp
* MontaukOS helpers for standalone Window Server apps
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <montauk/syscall.h>
#include "gui/canvas.hpp"
#include "gui/svg.hpp"
#include "gui/truetype.hpp"
namespace gui {
struct WsWindow {
int id;
uint32_t* pixels;
int width;
int height;
int scale_factor;
bool closed;
WsWindow()
: id(-1), pixels(nullptr), width(0), height(0), scale_factor(1), closed(false) {}
bool create(const char* title, int w, int h) {
Montauk::WinCreateResult wres;
if (montauk::win_create(title, w, h, &wres) < 0 || wres.id < 0)
return false;
id = wres.id;
pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
width = w;
height = h;
scale_factor = montauk::win_getscale();
closed = false;
return true;
}
int poll(Montauk::WinEvent* ev) {
if (id < 0 || closed) return -1;
int r = montauk::win_poll(id, ev);
if (r <= 0) return r;
if (ev->type == 2) {
width = ev->resize.w;
height = ev->resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(id, width, height);
} else if (ev->type == 3) {
closed = true;
} else if (ev->type == 4) {
scale_factor = ev->scale.scale;
}
return r;
}
Canvas canvas() const {
return Canvas(pixels, width, height);
}
void present() const {
if (id >= 0)
montauk::win_present(id);
}
void set_cursor(int cursor) const {
if (id >= 0)
montauk::win_setcursor(id, cursor);
}
void destroy() {
if (id >= 0)
montauk::win_destroy(id);
id = -1;
pixels = nullptr;
width = 0;
height = 0;
closed = true;
}
};
inline int text_width(TrueTypeFont* font, const char* text, int size) {
if (font && font->valid)
return font->measure_text(text, size);
int len = 0;
while (text && text[len]) len++;
return len * FONT_WIDTH;
}
inline int text_height(TrueTypeFont* font, int size) {
if (font && font->valid) {
GlyphCache* cache = font->get_cache(size);
if (cache)
return cache->ascent - cache->descent;
}
return size;
}
inline void draw_text(Canvas& c, TrueTypeFont* font, int x, int y,
const char* text, Color color, int size) {
if (!text || !text[0]) return;
if (font && font->valid)
font->draw_to_buffer(c.pixels, c.w, c.h, x, y, text, color, size);
}
inline void fill_circle(Canvas& c, int cx, int cy, int r, Color color) {
if (r <= 0) return;
for (int dy = -r; dy <= r; dy++) {
for (int dx = -r; dx <= r; dx++) {
if (dx * dx + dy * dy <= r * r)
c.put_pixel(cx + dx, cy + dy, color);
}
}
}
inline void draw_button(Canvas& c, TrueTypeFont* font, int x, int y, int w, int h,
const char* label, Color bg, Color fg, int radius,
int size = 16) {
c.fill_rounded_rect(x, y, w, h, radius, bg);
int tw = text_width(font, label, size);
int th = text_height(font, size);
draw_text(c, font, x + (w - tw) / 2, y + (h - th) / 2, label, fg, size);
}
inline void draw_icon(Canvas& c, int x, int y, const SvgIcon& icon) {
c.icon(x, y, icon);
}
} // namespace gui