diff --git a/programs/include/gui/standalone.hpp b/programs/include/gui/standalone.hpp new file mode 100644 index 0000000..36e090e --- /dev/null +++ b/programs/include/gui/standalone.hpp @@ -0,0 +1,131 @@ +/* + * standalone.hpp + * MontaukOS helpers for standalone Window Server apps + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#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 diff --git a/programs/src/bluetooth/main.cpp b/programs/src/bluetooth/main.cpp index ad14c7d..2d6a9e6 100644 --- a/programs/src/bluetooth/main.cpp +++ b/programs/src/bluetooth/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include 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, - tab_labels[i], tc, FONT_SIZE); + 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,9 +240,10 @@ 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, - msg, DIM_TEXT, FONT_SIZE); + 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++) { int ry = content_y + i * ROW_H - g_scroll; @@ -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, - addr_str, DIM_TEXT, FONT_SIZE_SM); + 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,9 +297,10 @@ 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, - msg, DIM_TEXT, FONT_SIZE); + 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++) { int ry = list_top + i * ROW_H - g_scroll; @@ -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,35 +324,35 @@ 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, - detail, DIM_TEXT, FONT_SIZE_SM); + 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 bool conn = is_connected(g_scan[i].bdAddr); 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); } diff --git a/programs/src/devexplorer/main.cpp b/programs/src/devexplorer/main.cpp index c8c8315..4ffe5ea 100644 --- a/programs/src/devexplorer/main.cpp +++ b/programs/src/devexplorer/main.cpp @@ -5,6 +5,7 @@ */ #include "devexplorer.h" +#include // ============================================================================ // 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); } diff --git a/programs/src/disks/main.cpp b/programs/src/disks/main.cpp index 53775c0..160ef20 100644 --- a/programs/src/disks/main.cpp +++ b/programs/src/disks/main.cpp @@ -5,6 +5,7 @@ */ #include "disks.h" +#include // ============================================================================ // 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); } diff --git a/programs/src/fontpreview/main.cpp b/programs/src/fontpreview/main.cpp index 425274b..c8fba47 100644 --- a/programs/src/fontpreview/main.cpp +++ b/programs/src/fontpreview/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include 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); } diff --git a/programs/src/imageviewer/main.cpp b/programs/src/imageviewer/main.cpp index 6fd023a..daa951b 100644 --- a/programs/src/imageviewer/main.cpp +++ b/programs/src/imageviewer/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include 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, - bx + (w - tw) / 2, TB_BTN_Y + (TB_BTN_SIZE - HEADER_FONT) / 2, - label, HEADER_TEXT, HEADER_FONT); + 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,9 +364,8 @@ 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, - zoom_label, HEADER_TEXT, HEADER_FONT); + 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); } diff --git a/programs/src/installer/main.cpp b/programs/src/installer/main.cpp index 5c102d1..4e5dc00 100644 --- a/programs/src/installer/main.cpp +++ b/programs/src/installer/main.cpp @@ -5,6 +5,7 @@ */ #include "installer.h" +#include // ============================================================================ // 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); } diff --git a/programs/src/paint/main.cpp b/programs/src/paint/main.cpp index 0bec453..d5a3ddf 100644 --- a/programs/src/paint/main.cpp +++ b/programs/src/paint/main.cpp @@ -5,6 +5,7 @@ */ #include "paint.h" +#include // ============================================================================ // 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); } diff --git a/programs/src/pdfviewer/main.cpp b/programs/src/pdfviewer/main.cpp index dd90cfd..cb47196 100644 --- a/programs/src/pdfviewer/main.cpp +++ b/programs/src/pdfviewer/main.cpp @@ -5,6 +5,7 @@ */ #include "pdfviewer.h" +#include // ============================================================================ // 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); } diff --git a/programs/src/spreadsheet/main.cpp b/programs/src/spreadsheet/main.cpp index 80ea820..fefbfb5 100644 --- a/programs/src/spreadsheet/main.cpp +++ b/programs/src/spreadsheet/main.cpp @@ -5,6 +5,7 @@ */ #include "spreadsheet.h" +#include // ============================================================================ // 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); } diff --git a/programs/src/volume/main.cpp b/programs/src/volume/main.cpp index 3df1b73..0852388 100644 --- a/programs/src/volume/main.cpp +++ b/programs/src/volume/main.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include 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); } diff --git a/programs/src/weather/main.cpp b/programs/src/weather/main.cpp index e0f42e7..cb0fd73 100644 --- a/programs/src/weather/main.cpp +++ b/programs/src/weather/main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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 // ============================================================================ @@ -412,82 +318,64 @@ static constexpr Color WHITE_TEXT = Color::from_rgb(0xFF, 0xFF, 0xFF); // Rendering // ============================================================================ -static constexpr int BTN_W = 110; -static constexpr int BTN_H = 28; -static constexpr int BTN_RADIUS = 6; +static constexpr int BTN_W = 110; +static constexpr int BTN_H = 28; +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, - "Fetching weather data...", HINT_TEXT, 18); + 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, - "Click Refresh to check weather.", HINT_TEXT, 18); + 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); } diff --git a/programs/src/wikipedia/main.cpp b/programs/src/wikipedia/main.cpp index 80ed02d..126c5e9 100644 --- a/programs/src/wikipedia/main.cpp +++ b/programs/src/wikipedia/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -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,29 +436,28 @@ 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, - btn_x + (btn_w - stw) / 2, - sb_y + (sb_h - FONT_SIZE) / 2, - "Search", WHITE, FONT_SIZE); + draw_text(canvas, g_font, + btn_x + (btn_w - stw) / 2, + sb_y + (sb_h - FONT_SIZE) / 2, + "Search", WHITE, FONT_SIZE); } // ---- Content area ---- @@ -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, - "Type a topic and press Enter or click Search.", - HINT_COLOR, FONT_SIZE); + 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, - "Searching Wikipedia...", HINT_COLOR, FONT_SIZE); + 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,24 +559,19 @@ 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 (g_phase == AppPhase::DONE && g_line_count > 0) { - build_display_lines(g_title, g_extract_buf, g_extract_len); - } + 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); } } @@ -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); } diff --git a/template/docs/gui-apps.md b/template/docs/gui-apps.md index 9e76e56..6cc4c3b 100644 --- a/template/docs/gui-apps.md +++ b/template/docs/gui-apps.md @@ -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 `. 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 diff --git a/template/docs/syscalls.md b/template/docs/syscalls.md index 576274e..a2cd487 100644 --- a/template/docs/syscalls.md +++ b/template/docs/syscalls.md @@ -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; }; }; ``` diff --git a/template/sysroot/include/gui/standalone.hpp b/template/sysroot/include/gui/standalone.hpp new file mode 100644 index 0000000..36e090e --- /dev/null +++ b/template/sysroot/include/gui/standalone.hpp @@ -0,0 +1,131 @@ +/* + * standalone.hpp + * MontaukOS helpers for standalone Window Server apps + * Copyright (c) 2026 Daniel Hammer +*/ + +#pragma once +#include +#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