From 91b09e8194fc949510d4a0293fc03cd60a16b7ac Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Fri, 10 Apr 2026 22:33:18 +0200 Subject: [PATCH] feat: standardize gui toolkit --- programs/src/desktop/apps/settings/about.cpp | 17 +- programs/src/desktop/apps/settings/app.cpp | 47 +-- .../src/desktop/apps/settings/appearance.cpp | 130 +++---- .../src/desktop/apps/settings/dialogs.cpp | 354 ++++++++++-------- .../src/desktop/apps/settings/display.cpp | 123 +++--- .../src/desktop/apps/settings/helpers.cpp | 48 --- .../apps/settings/settings_internal.hpp | 11 +- programs/src/desktop/apps/settings/users.cpp | 130 +++---- 8 files changed, 373 insertions(+), 487 deletions(-) diff --git a/programs/src/desktop/apps/settings/about.cpp b/programs/src/desktop/apps/settings/about.cpp index e24e121..a5d8ef4 100644 --- a/programs/src/desktop/apps/settings/about.cpp +++ b/programs/src/desktop/apps/settings/about.cpp @@ -11,40 +11,39 @@ namespace settings_app { void settings_draw_about(Canvas& c, SettingsState* st) { st->uptime_ms = montauk::get_milliseconds(); - Color dim = Color::from_rgb(0x88, 0x88, 0x88); + mtk::Theme theme = settings_theme(st); int x = 16; int y = 20; char line[128]; int sfh = system_font_height(); int line_h = sfh + 6; - // OS name in 2x size - c.text_2x(x, y, st->sys_info.osName, st->desktop->settings.accent_color); + c.text_2x(x, y, st->sys_info.osName, theme.accent); int large_h = (fonts::system_font && fonts::system_font->valid) ? fonts::system_font->get_line_height(fonts::LARGE_SIZE) : (FONT_HEIGHT * 2); y += large_h + 8; snprintf(line, sizeof(line), "Version %s", st->sys_info.osVersion); - c.text(x, y, line, colors::TEXT_COLOR); + c.text(x, y, line, theme.text); y += line_h + 8; - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 12; snprintf(line, sizeof(line), "API version: %d", (int)st->sys_info.apiVersion); - c.kv_line(x, &y, line, colors::TEXT_COLOR, line_h); + c.kv_line(x, &y, line, theme.text, line_h); int up_sec = (int)(st->uptime_ms / 1000); int up_min = up_sec / 60; int up_hr = up_min / 60; snprintf(line, sizeof(line), "Uptime: %d:%02d:%02d", up_hr, up_min % 60, up_sec % 60); - c.kv_line(x, &y, line, colors::TEXT_COLOR, line_h); + c.kv_line(x, &y, line, theme.text, line_h); y += 8; - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 12; - c.text(x, y, "Copyright (c) 2026 Daniel Hammer", dim); + c.text(x, y, "Copyright (c) 2026 Daniel Hammer", theme.text_muted); } } // namespace settings_app diff --git a/programs/src/desktop/apps/settings/app.cpp b/programs/src/desktop/apps/settings/app.cpp index 93fa659..c5bbec9 100644 --- a/programs/src/desktop/apps/settings/app.cpp +++ b/programs/src/desktop/apps/settings/app.cpp @@ -12,32 +12,11 @@ static void settings_on_draw(Window* win, Framebuffer& fb) { SettingsState* st = (SettingsState*)win->app_data; if (!st) return; - Canvas c(win); - c.fill(colors::WINDOW_BG); - - Color accent = st->desktop->settings.accent_color; - int sfh = system_font_height(); - - // Draw tab bar background - c.fill_rect(0, 0, c.w, TAB_BAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5)); - c.hline(0, TAB_BAR_H - 1, c.w, colors::BORDER); - - // Draw tabs - int tab_w = c.w / TAB_COUNT; - for (int i = 0; i < TAB_COUNT; i++) { - int tx = i * tab_w; - bool active = (i == st->active_tab); - - if (active) { - c.fill_rect(tx, 0, tab_w, TAB_BAR_H, colors::WINDOW_BG); - // Active tab underline - c.fill_rect(tx + 4, TAB_BAR_H - 3, tab_w - 8, 3, accent); - } - - int tw = text_width(tab_labels[i]); - Color tc = active ? accent : Color::from_rgb(0x66, 0x66, 0x66); - c.text(tx + (tab_w - tw) / 2, (TAB_BAR_H - sfh) / 2, tab_labels[i], tc); - } + mtk::DesktopHost host(win); + Canvas c = host.canvas(); + mtk::Theme theme = settings_theme(st); + c.fill(theme.window_bg); + mtk::draw_tab_bar(c, {0, 0, c.w, TAB_BAR_H}, tab_labels, TAB_COUNT, st->active_tab, theme); // Draw tab content below the tab bar Canvas content(win->content + TAB_BAR_H * win->content_w, @@ -55,17 +34,15 @@ static void settings_on_mouse(Window* win, MouseEvent& ev) { SettingsState* st = (SettingsState*)win->app_data; if (!st || !ev.left_pressed()) return; - Rect cr = win->content_rect(); - int mx = ev.x - cr.x; - int my = ev.y - cr.y; + mtk::DesktopHost host(win); + int mx = 0; + int my = 0; + if (!host.map_mouse(ev, &mx, &my)) return; // Tab bar click - if (my >= 0 && my < TAB_BAR_H) { - int tab_w = win->content_w / TAB_COUNT; - int tab = mx / tab_w; - if (tab >= 0 && tab < TAB_COUNT) { - st->active_tab = tab; - } + int tab = mtk::hit_tab_bar({0, 0, win->content_w, TAB_BAR_H}, TAB_COUNT, mx, my); + if (tab >= 0) { + st->active_tab = tab; return; } diff --git a/programs/src/desktop/apps/settings/appearance.cpp b/programs/src/desktop/apps/settings/appearance.cpp index def5972..a728aca 100644 --- a/programs/src/desktop/apps/settings/appearance.cpp +++ b/programs/src/desktop/apps/settings/appearance.cpp @@ -10,48 +10,41 @@ namespace settings_app { void settings_draw_appearance(Canvas& c, SettingsState* st) { DesktopSettings& s = st->desktop->settings; - Color accent = s.accent_color; - Color dim = Color::from_rgb(0x88, 0x88, 0x88); + mtk::Theme theme = settings_theme(st); int x = 16; int y = 12; int sfh = system_font_height(); int line_h = sfh + 10; - // Section: Background - c.text(x, y, "Background", colors::TEXT_COLOR); + c.text(x, y, "Background", theme.text); y += line_h; - // Radio buttons: Gradient / Solid Color / Image - bool mode_grad = s.bg_gradient && !s.bg_image; + bool mode_grad = s.bg_gradient && !s.bg_image; bool mode_solid = !s.bg_gradient && !s.bg_image; bool mode_image = s.bg_image; - draw_radio(c, x, y, mode_grad, accent); - c.text(x + 20, y + 2, "Gradient", colors::TEXT_COLOR); - - draw_radio(c, x + 120, y, mode_solid, accent); - c.text(x + 140, y + 2, "Solid", colors::TEXT_COLOR); - - draw_radio(c, x + 220, y, mode_image, accent); - c.text(x + 240, y + 2, "Image", colors::TEXT_COLOR); + Rect grad_option = {x, y, 100, 20}; + Rect solid_option = {x + 120, y, 90, 20}; + Rect image_option = {x + 220, y, 100, 20}; + mtk::draw_radio(c, grad_option, "Gradient", mode_grad, theme); + mtk::draw_radio(c, solid_option, "Solid", mode_solid, theme); + mtk::draw_radio(c, image_option, "Image", mode_image, theme); y += line_h + 4; if (mode_image) { - // Scan for images lazily if (!st->wp_scanned) { wallpaper_scan_home(st->desktop->home_dir, &st->wp_files); st->wp_scanned = true; } - c.text(x, y, "Wallpapers", dim); + c.text(x, y, "Wallpapers", theme.text_muted); y += sfh + 6; if (st->wp_files.count == 0) { - c.text(x + 8, y, "No .jpg files found", dim); + c.text(x + 8, y, "No .jpg files found", theme.text_muted); y += WP_ITEM_H; } else { for (int i = 0; i < st->wp_files.count && i < 8; i++) { - // Build full path for comparison char fullpath[256]; montauk::strcpy(fullpath, st->desktop->home_dir); str_append(fullpath, "/", 256); @@ -60,11 +53,9 @@ void settings_draw_appearance(Canvas& c, SettingsState* st) { bool selected = s.bg_image && montauk::streq(s.bg_image_path, fullpath); - if (selected) { - c.fill_rounded_rect(x, y, c.w - 2 * x, WP_ITEM_H, 3, accent); - } + Rect row = {x, y, c.w - 2 * x, WP_ITEM_H}; + mtk::draw_list_row(c, row, selected, (i % 2) == 0, theme); - // Truncate long filenames char label[40]; int nlen = montauk::slen(st->wp_files.names[i]); if (nlen > 35) { @@ -77,45 +68,48 @@ void settings_draw_appearance(Canvas& c, SettingsState* st) { montauk::strncpy(label, st->wp_files.names[i], 39); } - Color tc = selected ? colors::WHITE : colors::TEXT_COLOR; - c.text(x + 8, y + (WP_ITEM_H - sfh) / 2, label, tc); + c.text(x + 8, y + (WP_ITEM_H - sfh) / 2, label, + selected ? theme.text_inverse : theme.text); y += WP_ITEM_H; } } y += 6; } else if (mode_grad) { - // Top color swatches - c.text(x, y + 4, "Top", dim); - draw_swatch_row(c, x + 70, y, bg_palette, s.bg_grad_top, accent); + c.text(x, y + 4, "Top", theme.text_muted); + mtk::draw_swatch_row(c, x + 70, y, bg_palette, SWATCH_COUNT, + find_swatch(bg_palette, s.bg_grad_top), theme, + SWATCH_SIZE, SWATCH_GAP); y += SWATCH_SIZE + 14; - // Bottom color swatches - c.text(x, y + 4, "Bottom", dim); - draw_swatch_row(c, x + 70, y, bg_palette, s.bg_grad_bottom, accent); + c.text(x, y + 4, "Bottom", theme.text_muted); + mtk::draw_swatch_row(c, x + 70, y, bg_palette, SWATCH_COUNT, + find_swatch(bg_palette, s.bg_grad_bottom), theme, + SWATCH_SIZE, SWATCH_GAP); y += SWATCH_SIZE + 14; } else { - // Solid color swatches - c.text(x, y + 4, "Color", dim); - draw_swatch_row(c, x + 70, y, bg_palette, s.bg_solid, accent); + c.text(x, y + 4, "Color", theme.text_muted); + mtk::draw_swatch_row(c, x + 70, y, bg_palette, SWATCH_COUNT, + find_swatch(bg_palette, s.bg_solid), theme, + SWATCH_SIZE, SWATCH_GAP); y += SWATCH_SIZE + 14; } - // Separator - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 12; - // Panel color - c.text(x, y + 4, "Panel Color", colors::TEXT_COLOR); - draw_swatch_row(c, x + 110, y, panel_palette, s.panel_color, accent); + c.text(x, y + 4, "Panel Color", theme.text); + mtk::draw_swatch_row(c, x + 110, y, panel_palette, SWATCH_COUNT, + find_swatch(panel_palette, s.panel_color), theme, + SWATCH_SIZE, SWATCH_GAP); y += SWATCH_SIZE + 14; - // Separator - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 12; - // Accent color - c.text(x, y + 4, "Accent Color", colors::TEXT_COLOR); - draw_swatch_row(c, x + 110, y, accent_palette, s.accent_color, accent); + c.text(x, y + 4, "Accent Color", theme.text); + mtk::draw_swatch_row(c, x + 110, y, accent_palette, SWATCH_COUNT, + find_swatch(accent_palette, s.accent_color), theme, + SWATCH_SIZE, SWATCH_GAP); } bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, int cy) { @@ -125,28 +119,27 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in int line_h = sfh + 10; int y = 12; - bool mode_grad = s.bg_gradient && !s.bg_image; + bool mode_grad = s.bg_gradient && !s.bg_image; bool mode_image = s.bg_image; - // "Background" label y += line_h; - // Radio: Gradient - if (mx >= x && mx < x + 100 && cy >= y && cy < y + 16) { + Rect grad_option = {x, y, 100, 20}; + Rect solid_option = {x + 120, y, 90, 20}; + Rect image_option = {x + 220, y, 100, 20}; + if (grad_option.contains(mx, cy)) { s.bg_gradient = true; s.bg_image = false; settings_persist(st); return true; } - // Radio: Solid - if (mx >= x + 120 && mx < x + 210 && cy >= y && cy < y + 16) { + if (solid_option.contains(mx, cy)) { s.bg_gradient = false; s.bg_image = false; settings_persist(st); return true; } - // Radio: Image - if (mx >= x + 220 && mx < x + 320 && cy >= y && cy < y + 16) { + if (image_option.contains(mx, cy)) { s.bg_image = true; s.bg_gradient = false; if (!st->wp_scanned) { @@ -158,16 +151,11 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in } y += line_h + 4; - int idx; if (mode_image) { - // Wallpaper file list y += sfh + 6; - - // File list clicks for (int i = 0; i < st->wp_files.count && i < 8; i++) { - if (cy >= y && cy < y + WP_ITEM_H && - mx >= x && mx < win->content_w - x) { - // Build full path and load wallpaper + Rect row = {x, y, win->content_w - 2 * x, WP_ITEM_H}; + if (row.contains(mx, cy)) { char fullpath[256]; montauk::strcpy(fullpath, st->desktop->home_dir); str_append(fullpath, "/", 256); @@ -182,24 +170,24 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in if (st->wp_files.count == 0) y += WP_ITEM_H; y += 6; } else if (mode_grad) { - // Top swatches - if (swatch_hit(mx, cy, x + 70, y, &idx)) { + int idx = mtk::hit_swatch_row(mx, cy, x + 70, y, SWATCH_COUNT, SWATCH_SIZE, SWATCH_GAP); + if (idx >= 0) { s.bg_grad_top = bg_palette[idx]; settings_persist(st); return true; } y += SWATCH_SIZE + 14; - // Bottom swatches - if (swatch_hit(mx, cy, x + 70, y, &idx)) { + idx = mtk::hit_swatch_row(mx, cy, x + 70, y, SWATCH_COUNT, SWATCH_SIZE, SWATCH_GAP); + if (idx >= 0) { s.bg_grad_bottom = bg_palette[idx]; settings_persist(st); return true; } y += SWATCH_SIZE + 14; } else { - // Solid color swatches - if (swatch_hit(mx, cy, x + 70, y, &idx)) { + int idx = mtk::hit_swatch_row(mx, cy, x + 70, y, SWATCH_COUNT, SWATCH_SIZE, SWATCH_GAP); + if (idx >= 0) { s.bg_solid = bg_palette[idx]; settings_persist(st); return true; @@ -207,22 +195,18 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in y += SWATCH_SIZE + 14; } - // Separator y += 12; - - // Panel color swatches - if (swatch_hit(mx, cy, x + 110, y, &idx)) { + int idx = mtk::hit_swatch_row(mx, cy, x + 110, y, SWATCH_COUNT, SWATCH_SIZE, SWATCH_GAP); + if (idx >= 0) { s.panel_color = panel_palette[idx]; settings_persist(st); return true; } + y += SWATCH_SIZE + 14; - - // Separator y += 12; - - // Accent color swatches - if (swatch_hit(mx, cy, x + 110, y, &idx)) { + idx = mtk::hit_swatch_row(mx, cy, x + 110, y, SWATCH_COUNT, SWATCH_SIZE, SWATCH_GAP); + if (idx >= 0) { s.accent_color = accent_palette[idx]; settings_persist(st); return true; diff --git a/programs/src/desktop/apps/settings/dialogs.cpp b/programs/src/desktop/apps/settings/dialogs.cpp index 6a5e53a..42fe45f 100644 --- a/programs/src/desktop/apps/settings/dialogs.cpp +++ b/programs/src/desktop/apps/settings/dialogs.cpp @@ -9,11 +9,18 @@ namespace settings_app { static void dialog_append(char* buf, int* len, int max, char ch) { - if (*len < max - 1) { buf[*len] = ch; (*len)++; buf[*len] = '\0'; } + if (*len < max - 1) { + buf[*len] = ch; + (*len)++; + buf[*len] = '\0'; + } } static void dialog_backspace(char* buf, int* len) { - if (*len > 0) { (*len)--; buf[*len] = '\0'; } + if (*len > 0) { + (*len)--; + buf[*len] = '\0'; + } } static void dialog_close_self(DesktopState* ds, void* app_data) { @@ -25,32 +32,12 @@ static void dialog_close_self(DesktopState* ds, void* app_data) { } } -static void draw_input_field(Canvas& c, int x, int y, int w, int h, - const char* label, const char* value, - bool focused, bool masked, Color accent) { - int sfh = system_font_height(); - Color dim = Color::from_rgb(0x88, 0x88, 0x88); +static Rect dialog_primary_button_rect(int pad, int content_h) { + return {pad, content_h - USER_BTN_H - pad, 80, USER_BTN_H}; +} - c.text(x, y, label, dim); - y += sfh + 2; - Color border = focused ? accent : colors::BORDER; - c.fill_rounded_rect(x, y, w, h, 3, Color::from_rgb(0xF5, 0xF5, 0xF5)); - c.rect(x, y, w, h, border); - - if (masked) { - int vlen = montauk::slen(value); - char dots[65]; - for (int i = 0; i < vlen && i < 64; i++) dots[i] = '*'; - dots[vlen < 64 ? vlen : 64] = '\0'; - c.text(x + 8, y + (h - sfh) / 2, dots, colors::TEXT_COLOR); - } else { - c.text(x + 8, y + (h - sfh) / 2, value, colors::TEXT_COLOR); - } - - if (focused) { - int tw = masked ? text_width("*") * montauk::slen(value) : text_width(value); - c.fill_rect(x + 8 + tw, y + 6, 2, h - 12, accent); - } +static Rect dialog_secondary_button_rect(int pad, int content_h) { + return {pad + 88, content_h - USER_BTN_H - pad, 80, USER_BTN_H}; } // ============================================================================ @@ -61,7 +48,7 @@ struct AddUserDialogState { DesktopState* ds; SettingsState* parent; Color accent; - int field; // 0=username, 1=display_name, 2=password + int field; char username[32]; int username_len; char display[64]; int display_len; char password[64]; int password_len; @@ -74,50 +61,46 @@ static void adduser_on_draw(Window* win, Framebuffer& fb) { if (!st) return; Canvas c(win); - c.fill(colors::WINDOW_BG); - Color accent = st->accent; - int sfh = system_font_height(); + mtk::Theme theme = mtk::make_theme(st->accent); + c.fill(theme.window_bg); + int pad = 16; int fw = c.w - 2 * pad; int y = pad; + int sfh = system_font_height(); - // Fields - draw_input_field(c, pad, y, fw, USER_FIELD_H, "Username", st->username, - st->field == 0, false, accent); - y += sfh + 2 + USER_FIELD_H + 10; + mtk::draw_labeled_text_field(c, pad, y, fw, "Username", st->username, + st->username_len, st->field == 0, false, theme, USER_FIELD_H); + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 10; - draw_input_field(c, pad, y, fw, USER_FIELD_H, "Display Name", st->display, - st->field == 1, false, accent); - y += sfh + 2 + USER_FIELD_H + 10; + mtk::draw_labeled_text_field(c, pad, y, fw, "Display Name", st->display, + st->display_len, st->field == 1, false, theme, USER_FIELD_H); + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 10; - draw_input_field(c, pad, y, fw, USER_FIELD_H, "Password", st->password, - st->field == 2, true, accent); - y += sfh + 2 + USER_FIELD_H + 12; + mtk::draw_labeled_text_field(c, pad, y, fw, "Password", st->password, + st->password_len, st->field == 2, true, theme, USER_FIELD_H); + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 12; - // Role toggle - Color dim = Color::from_rgb(0x88, 0x88, 0x88); - c.text(pad, y + 4, "Role:", dim); - int rbx = pad + 60; - draw_toggle_btn(c, rbx, y, 60, USER_BTN_H, "User", !st->role_admin, accent); - draw_toggle_btn(c, rbx + 68, y, 60, USER_BTN_H, "Admin", st->role_admin, accent); + c.text(pad, y + 4, "Role:", theme.text_muted); + Rect user_btn = {pad + 60, y, 60, USER_BTN_H}; + Rect admin_btn = {pad + 128, y, 60, USER_BTN_H}; + mtk::draw_button(c, user_btn, "User", mtk::BUTTON_SECONDARY, + mtk::widget_state(!st->role_admin), theme); + mtk::draw_button(c, admin_btn, "Admin", mtk::BUTTON_SECONDARY, + mtk::widget_state(st->role_admin), theme); y += USER_BTN_H + 14; - // Error message if (st->error[0]) { - c.text(pad, y, st->error, Color::from_rgb(0xD0, 0x3E, 0x3E)); + c.text(pad, y, st->error, theme.danger); y += sfh + 6; } - // Buttons at bottom - int btn_y = c.h - USER_BTN_H - pad; - c.fill_rounded_rect(pad, btn_y, 80, USER_BTN_H, 4, accent); - int tw = text_width("Create"); - c.text(pad + (80 - tw) / 2, btn_y + (USER_BTN_H - sfh) / 2, "Create", colors::WHITE); - - c.fill_rounded_rect(pad + 88, btn_y, 80, USER_BTN_H, 4, colors::WINDOW_BG); - c.rect(pad + 88, btn_y, 80, USER_BTN_H, colors::BORDER); - tw = text_width("Cancel"); - c.text(pad + 88 + (80 - tw) / 2, btn_y + (USER_BTN_H - sfh) / 2, "Cancel", colors::TEXT_COLOR); + mtk::draw_modal_actions(c, + dialog_primary_button_rect(pad, c.h), "Create", + mtk::BUTTON_PRIMARY, mtk::widget_state(), + dialog_secondary_button_rect(pad, c.h), "Cancel", + mtk::widget_state(), + theme); } static void adduser_submit(AddUserDialogState* st) { @@ -129,6 +112,7 @@ static void adduser_submit(AddUserDialogState* st) { montauk::strcpy(st->error, "Password is required"); return; } + const char* dname = st->display_len > 0 ? st->display : st->username; const char* role = st->role_admin ? "admin" : "user"; if (montauk::user::create_user(st->username, dname, st->password, role)) { @@ -144,42 +128,48 @@ static void adduser_on_mouse(Window* win, MouseEvent& ev) { AddUserDialogState* st = (AddUserDialogState*)win->app_data; if (!st || !ev.left_pressed()) return; - Rect cr = win->content_rect(); - int mx = ev.x - cr.x; - int my = ev.y - cr.y; + mtk::DesktopHost host(win); + int mx = 0; + int my = 0; + if (!host.map_mouse(ev, &mx, &my)) return; + + mtk::Theme theme = mtk::make_theme(st->accent); int pad = 16; - int sfh = system_font_height(); + int fw = win->content_w - 2 * pad; int y = pad; - // Username field hit - y += sfh + 2; - if (my >= y && my < y + USER_FIELD_H) { st->field = 0; return; } - y += USER_FIELD_H + 10; + Rect username_rect = mtk::labeled_text_input_rect(pad, y, fw, theme, USER_FIELD_H); + if (username_rect.contains(mx, my)) { st->field = 0; return; } + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 10; - // Display field hit - y += sfh + 2; - if (my >= y && my < y + USER_FIELD_H) { st->field = 1; return; } - y += USER_FIELD_H + 10; + Rect display_rect = mtk::labeled_text_input_rect(pad, y, fw, theme, USER_FIELD_H); + if (display_rect.contains(mx, my)) { st->field = 1; return; } + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 10; - // Password field hit - y += sfh + 2; - if (my >= y && my < y + USER_FIELD_H) { st->field = 2; return; } - y += USER_FIELD_H + 12; + Rect password_rect = mtk::labeled_text_input_rect(pad, y, fw, theme, USER_FIELD_H); + if (password_rect.contains(mx, my)) { st->field = 2; return; } + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 12; - // Role toggle - int rbx = pad + 60; - if (mx >= rbx && mx < rbx + 60 && my >= y && my < y + USER_BTN_H) { - st->role_admin = false; return; + Rect user_btn = {pad + 60, y, 60, USER_BTN_H}; + Rect admin_btn = {pad + 128, y, 60, USER_BTN_H}; + if (user_btn.contains(mx, my)) { + st->role_admin = false; + return; } - if (mx >= rbx + 68 && mx < rbx + 128 && my >= y && my < y + USER_BTN_H) { - st->role_admin = true; return; + if (admin_btn.contains(mx, my)) { + st->role_admin = true; + return; } - // Bottom buttons - int btn_y = win->content_h - USER_BTN_H - pad; - if (my >= btn_y && my < btn_y + USER_BTN_H) { - if (mx >= pad && mx < pad + 80) { adduser_submit(st); return; } - if (mx >= pad + 88 && mx < pad + 168) { dialog_close_self(st->ds, st); return; } + Rect create_btn = dialog_primary_button_rect(pad, win->content_h); + Rect cancel_btn = dialog_secondary_button_rect(pad, win->content_h); + if (create_btn.contains(mx, my)) { + adduser_submit(st); + return; + } + if (cancel_btn.contains(mx, my)) { + dialog_close_self(st->ds, st); + return; } } @@ -188,10 +178,17 @@ static void adduser_on_key(Window* win, const Montauk::KeyEvent& key) { if (!st || !key.pressed) return; if (key.ascii == '\n' || key.ascii == '\r' || key.scancode == 0x1C) { - adduser_submit(st); return; + adduser_submit(st); + return; + } + if (key.scancode == 0x01) { + dialog_close_self(st->ds, st); + return; + } + if (key.scancode == 0x0F) { + st->field = (st->field + 1) % 3; + return; } - if (key.scancode == 0x01) { dialog_close_self(st->ds, st); return; } - if (key.scancode == 0x0F) { st->field = (st->field + 1) % 3; return; } if (key.ascii == '\b' || key.scancode == 0x0E) { switch (st->field) { case 0: dialog_backspace(st->username, &st->username_len); break; @@ -210,12 +207,16 @@ static void adduser_on_key(Window* win, const Montauk::KeyEvent& key) { } static void adduser_on_close(Window* win) { - if (win->app_data) { montauk::mfree(win->app_data); win->app_data = nullptr; } + if (win->app_data) { + montauk::mfree(win->app_data); + win->app_data = nullptr; + } } void open_add_user_dialog(SettingsState* parent) { DesktopState* ds = parent->desktop; - int w = 340, h = 340; + int w = 340; + int h = 340; int wx = (ds->screen_w - w) / 2; int wy = (ds->screen_h - h) / 2; int idx = desktop_create_window(ds, "Add User", wx, wy, w, h); @@ -245,7 +246,7 @@ struct ChPwdDialogState { Color accent; char username[32]; char display_name[64]; - int field; // 0=new, 1=confirm + int field; char new_pwd[64]; int new_len; char confirm[64]; int confirm_len; char error[64]; @@ -256,42 +257,37 @@ static void chpwd_on_draw(Window* win, Framebuffer& fb) { if (!st) return; Canvas c(win); - c.fill(colors::WINDOW_BG); - Color accent = st->accent; - int sfh = system_font_height(); + mtk::Theme theme = mtk::make_theme(st->accent); + c.fill(theme.window_bg); + int pad = 16; int fw = c.w - 2 * pad; int y = pad; + int sfh = system_font_height(); - // Title char title[80]; snprintf(title, sizeof(title), "Change password for %s", st->display_name); - c.text(pad, y, title, colors::TEXT_COLOR); + c.text(pad, y, title, theme.text); y += sfh + 12; - draw_input_field(c, pad, y, fw, USER_FIELD_H, "New Password", st->new_pwd, - st->field == 0, true, accent); - y += sfh + 2 + USER_FIELD_H + 10; + mtk::draw_labeled_text_field(c, pad, y, fw, "New Password", st->new_pwd, + st->new_len, st->field == 0, true, theme, USER_FIELD_H); + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 10; - draw_input_field(c, pad, y, fw, USER_FIELD_H, "Confirm Password", st->confirm, - st->field == 1, true, accent); - y += sfh + 2 + USER_FIELD_H + 12; + mtk::draw_labeled_text_field(c, pad, y, fw, "Confirm Password", st->confirm, + st->confirm_len, st->field == 1, true, theme, USER_FIELD_H); + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 12; - // Error if (st->error[0]) { - c.text(pad, y, st->error, Color::from_rgb(0xD0, 0x3E, 0x3E)); + c.text(pad, y, st->error, theme.danger); } - // Buttons at bottom - int btn_y = c.h - USER_BTN_H - pad; - c.fill_rounded_rect(pad, btn_y, 80, USER_BTN_H, 4, accent); - int tw = text_width("Save"); - c.text(pad + (80 - tw) / 2, btn_y + (USER_BTN_H - sfh) / 2, "Save", colors::WHITE); - - c.fill_rounded_rect(pad + 88, btn_y, 80, USER_BTN_H, 4, colors::WINDOW_BG); - c.rect(pad + 88, btn_y, 80, USER_BTN_H, colors::BORDER); - tw = text_width("Cancel"); - c.text(pad + 88 + (80 - tw) / 2, btn_y + (USER_BTN_H - sfh) / 2, "Cancel", colors::TEXT_COLOR); + mtk::draw_modal_actions(c, + dialog_primary_button_rect(pad, c.h), "Save", + mtk::BUTTON_PRIMARY, mtk::widget_state(), + dialog_secondary_button_rect(pad, c.h), "Cancel", + mtk::widget_state(), + theme); } static void chpwd_submit(ChPwdDialogState* st) { @@ -303,6 +299,7 @@ static void chpwd_submit(ChPwdDialogState* st) { montauk::strcpy(st->error, "Passwords don't match"); return; } + montauk::user::change_password(st->username, st->new_pwd); settings_set_status(st->parent, "Password changed"); dialog_close_self(st->ds, st); @@ -312,27 +309,32 @@ static void chpwd_on_mouse(Window* win, MouseEvent& ev) { ChPwdDialogState* st = (ChPwdDialogState*)win->app_data; if (!st || !ev.left_pressed()) return; - Rect cr = win->content_rect(); - int mx = ev.x - cr.x; - int my = ev.y - cr.y; + mtk::DesktopHost host(win); + int mx = 0; + int my = 0; + if (!host.map_mouse(ev, &mx, &my)) return; + + mtk::Theme theme = mtk::make_theme(st->accent); int pad = 16; - int sfh = system_font_height(); - int y = pad + sfh + 12; + int fw = win->content_w - 2 * pad; + int y = pad + system_font_height() + 12; - // New password field - y += sfh + 2; - if (my >= y && my < y + USER_FIELD_H) { st->field = 0; return; } - y += USER_FIELD_H + 10; + Rect new_rect = mtk::labeled_text_input_rect(pad, y, fw, theme, USER_FIELD_H); + if (new_rect.contains(mx, my)) { st->field = 0; return; } + y += mtk::labeled_text_height(theme, USER_FIELD_H) + 10; - // Confirm field - y += sfh + 2; - if (my >= y && my < y + USER_FIELD_H) { st->field = 1; return; } + Rect confirm_rect = mtk::labeled_text_input_rect(pad, y, fw, theme, USER_FIELD_H); + if (confirm_rect.contains(mx, my)) { st->field = 1; return; } - // Bottom buttons - int btn_y = win->content_h - USER_BTN_H - pad; - if (my >= btn_y && my < btn_y + USER_BTN_H) { - if (mx >= pad && mx < pad + 80) { chpwd_submit(st); return; } - if (mx >= pad + 88 && mx < pad + 168) { dialog_close_self(st->ds, st); return; } + Rect save_btn = dialog_primary_button_rect(pad, win->content_h); + Rect cancel_btn = dialog_secondary_button_rect(pad, win->content_h); + if (save_btn.contains(mx, my)) { + chpwd_submit(st); + return; + } + if (cancel_btn.contains(mx, my)) { + dialog_close_self(st->ds, st); + return; } } @@ -341,10 +343,17 @@ static void chpwd_on_key(Window* win, const Montauk::KeyEvent& key) { if (!st || !key.pressed) return; if (key.ascii == '\n' || key.ascii == '\r' || key.scancode == 0x1C) { - chpwd_submit(st); return; + chpwd_submit(st); + return; + } + if (key.scancode == 0x01) { + dialog_close_self(st->ds, st); + return; + } + if (key.scancode == 0x0F) { + st->field = (st->field + 1) % 2; + return; } - if (key.scancode == 0x01) { dialog_close_self(st->ds, st); return; } - if (key.scancode == 0x0F) { st->field = (st->field + 1) % 2; return; } if (key.ascii == '\b' || key.scancode == 0x0E) { if (st->field == 0) dialog_backspace(st->new_pwd, &st->new_len); else dialog_backspace(st->confirm, &st->confirm_len); @@ -357,13 +366,18 @@ static void chpwd_on_key(Window* win, const Montauk::KeyEvent& key) { } static void chpwd_on_close(Window* win) { - if (win->app_data) { montauk::mfree(win->app_data); win->app_data = nullptr; } + if (win->app_data) { + montauk::mfree(win->app_data); + win->app_data = nullptr; + } } void open_change_pwd_dialog(SettingsState* parent) { if (parent->selected_user < 0) return; + DesktopState* ds = parent->desktop; - int w = 340, h = 260; + int w = 340; + int h = 260; int wx = (ds->screen_w - w) / 2; int wy = (ds->screen_h - h) / 2; int idx = desktop_create_window(ds, "Change Password", wx, wy, w, h); @@ -393,7 +407,8 @@ struct DeleteUserDialogState { DesktopState* ds; SettingsState* parent; char username[32]; - bool hover_delete, hover_cancel; + bool hover_delete; + bool hover_cancel; }; static void deluser_on_draw(Window* win, Framebuffer& fb) { @@ -401,54 +416,60 @@ static void deluser_on_draw(Window* win, Framebuffer& fb) { if (!st) return; Canvas c(win); - c.fill(colors::WINDOW_BG); - int sfh = system_font_height(); + mtk::Theme theme = settings_theme(st->parent); + c.fill(theme.window_bg); + int sfh = system_font_height(); char msg[96]; snprintf(msg, sizeof(msg), "Delete user \"%s\"?", st->username); int tw = text_width(msg); - c.text((c.w - tw) / 2, 24, msg, colors::TEXT_COLOR); + c.text((c.w - tw) / 2, 24, msg, theme.text); const char* warn = "This action cannot be undone."; tw = text_width(warn); - c.text((c.w - tw) / 2, 24 + sfh + 8, warn, Color::from_rgb(0x88, 0x88, 0x88)); + c.text((c.w - tw) / 2, 24 + sfh + 8, warn, theme.text_muted); - // Buttons - int btn_w = 100, btn_h = 32; + int btn_w = 100; + int btn_h = 32; int btn_y = c.h - btn_h - 20; int gap = 20; int total = btn_w * 2 + gap; int bx = (c.w - total) / 2; - Color del_bg = st->hover_delete - ? Color::from_rgb(0xDD, 0x44, 0x44) - : Color::from_rgb(0xD0, 0x3E, 0x3E); - c.button(bx, btn_y, btn_w, btn_h, "Delete", del_bg, colors::WHITE, 4); - - Color cancel_bg = st->hover_cancel - ? Color::from_rgb(0x99, 0x99, 0x99) - : Color::from_rgb(0x88, 0x88, 0x88); - c.button(bx + btn_w + gap, btn_y, btn_w, btn_h, "Cancel", cancel_bg, colors::WHITE, 4); + Rect delete_btn = {bx, btn_y, btn_w, btn_h}; + Rect cancel_btn = {bx + btn_w + gap, btn_y, btn_w, btn_h}; + mtk::draw_button(c, delete_btn, "Delete", mtk::BUTTON_DANGER, + mtk::widget_state(false, st->hover_delete), theme); + mtk::draw_button(c, cancel_btn, "Cancel", mtk::BUTTON_SECONDARY, + mtk::widget_state(false, st->hover_cancel), theme); } static void deluser_on_mouse(Window* win, MouseEvent& ev) { DeleteUserDialogState* st = (DeleteUserDialogState*)win->app_data; if (!st) return; - Rect cr = win->content_rect(); - int lx = ev.x - cr.x; - int ly = ev.y - cr.y; + mtk::DesktopHost host(win); + int mx = 0; + int my = 0; + if (!host.map_mouse(ev, &mx, &my)) return; - int btn_w = 100, btn_h = 32; + int btn_w = 100; + int btn_h = 32; int btn_y = win->content_h - btn_h - 20; int gap = 20; int total = btn_w * 2 + gap; int bx = (win->content_w - total) / 2; - Rect db = {bx, btn_y, btn_w, btn_h}; - Rect cb = {bx + btn_w + gap, btn_y, btn_w, btn_h}; - st->hover_delete = db.contains(lx, ly); - st->hover_cancel = cb.contains(lx, ly); + Rect delete_btn = {bx, btn_y, btn_w, btn_h}; + Rect cancel_btn = {bx + btn_w + gap, btn_y, btn_w, btn_h}; + + bool old_delete = st->hover_delete; + bool old_cancel = st->hover_cancel; + st->hover_delete = delete_btn.contains(mx, my); + st->hover_cancel = cancel_btn.contains(mx, my); + if (old_delete != st->hover_delete || old_cancel != st->hover_cancel) { + host.invalidate(); + } if (ev.left_pressed()) { if (st->hover_delete) { @@ -483,20 +504,23 @@ static void deluser_on_key(Window* win, const Montauk::KeyEvent& key) { } static void deluser_on_close(Window* win) { - if (win->app_data) { montauk::mfree(win->app_data); win->app_data = nullptr; } + if (win->app_data) { + montauk::mfree(win->app_data); + win->app_data = nullptr; + } } void open_delete_user_dialog(SettingsState* parent) { if (parent->selected_user < 0) return; - DesktopState* ds = parent->desktop; - // Don't allow deleting yourself + DesktopState* ds = parent->desktop; if (montauk::streq(parent->users[parent->selected_user].username, ds->current_user)) { settings_set_status(parent, "Cannot delete current user"); return; } - int w = 300, h = 150; + int w = 300; + int h = 150; int wx = (ds->screen_w - w) / 2; int wy = (ds->screen_h - h) / 2; int idx = desktop_create_window(ds, "Delete User", wx, wy, w, h); diff --git a/programs/src/desktop/apps/settings/display.cpp b/programs/src/desktop/apps/settings/display.cpp index 759b9d0..575692c 100644 --- a/programs/src/desktop/apps/settings/display.cpp +++ b/programs/src/desktop/apps/settings/display.cpp @@ -10,78 +10,75 @@ namespace settings_app { void apply_ui_scale(int scale) { switch (scale) { - case 0: fonts::UI_SIZE=14; fonts::TITLE_SIZE=14; fonts::TERM_SIZE=14; fonts::LARGE_SIZE=22; break; - case 2: fonts::UI_SIZE=22; fonts::TITLE_SIZE=22; fonts::TERM_SIZE=22; fonts::LARGE_SIZE=34; break; - default: fonts::UI_SIZE=18; fonts::TITLE_SIZE=18; fonts::TERM_SIZE=18; fonts::LARGE_SIZE=28; break; + case 0: fonts::UI_SIZE = 14; fonts::TITLE_SIZE = 14; fonts::TERM_SIZE = 14; fonts::LARGE_SIZE = 22; break; + case 2: fonts::UI_SIZE = 22; fonts::TITLE_SIZE = 22; fonts::TERM_SIZE = 22; fonts::LARGE_SIZE = 34; break; + default: fonts::UI_SIZE = 18; fonts::TITLE_SIZE = 18; fonts::TERM_SIZE = 18; fonts::LARGE_SIZE = 28; break; } } void settings_draw_display(Canvas& c, SettingsState* st) { DesktopSettings& s = st->desktop->settings; - Color accent = s.accent_color; + mtk::Theme theme = settings_theme(st); int x = 16; int y = 20; int btn_w = 60; int btn_h = 28; - - // Window Shadows - c.text(x, y + 6, "Window Shadows", colors::TEXT_COLOR); int bx = x + 180; - draw_toggle_btn(c, bx, y, btn_w, btn_h, "On", s.show_shadows, accent); - draw_toggle_btn(c, bx + btn_w + 8, y, btn_w, btn_h, "Off", !s.show_shadows, accent); + + c.text(x, y + 6, "Window Shadows", theme.text); + mtk::draw_button(c, {bx, y, btn_w, btn_h}, "On", mtk::BUTTON_SECONDARY, + mtk::widget_state(s.show_shadows), theme); + mtk::draw_button(c, {bx + btn_w + 8, y, btn_w, btn_h}, "Off", mtk::BUTTON_SECONDARY, + mtk::widget_state(!s.show_shadows), theme); y += btn_h + 20; - // Separator - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 16; - // Clock Format - c.text(x, y + 6, "Clock Format", colors::TEXT_COLOR); - bx = x + 180; - draw_toggle_btn(c, bx, y, btn_w, btn_h, "24h", s.clock_24h, accent); - draw_toggle_btn(c, bx + btn_w + 8, y, btn_w, btn_h, "12h", !s.clock_24h, accent); + c.text(x, y + 6, "Clock Format", theme.text); + mtk::draw_button(c, {bx, y, btn_w, btn_h}, "24h", mtk::BUTTON_SECONDARY, + mtk::widget_state(s.clock_24h), theme); + mtk::draw_button(c, {bx + btn_w + 8, y, btn_w, btn_h}, "12h", mtk::BUTTON_SECONDARY, + mtk::widget_state(!s.clock_24h), theme); y += btn_h + 20; - // Separator - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 16; - // UI Scale - c.text(x, y + 6, "UI Scale", colors::TEXT_COLOR); - bx = x + 180; + c.text(x, y + 6, "UI Scale", theme.text); int sbw = 68; - draw_toggle_btn(c, bx, y, sbw, btn_h, "Small", s.ui_scale == 0, accent); - draw_toggle_btn(c, bx + sbw + 8, y, sbw, btn_h, "Default", s.ui_scale == 1, accent); - draw_toggle_btn(c, bx + (sbw + 8) * 2, y, sbw, btn_h, "Large", s.ui_scale == 2, accent); + mtk::draw_button(c, {bx, y, sbw, btn_h}, "Small", mtk::BUTTON_SECONDARY, + mtk::widget_state(s.ui_scale == 0), theme); + mtk::draw_button(c, {bx + sbw + 8, y, sbw, btn_h}, "Default", mtk::BUTTON_SECONDARY, + mtk::widget_state(s.ui_scale == 1), theme); + mtk::draw_button(c, {bx + (sbw + 8) * 2, y, sbw, btn_h}, "Large", mtk::BUTTON_SECONDARY, + mtk::widget_state(s.ui_scale == 2), theme); y += btn_h + 20; - // Separator - c.hline(x, y, c.w - 2 * x, colors::BORDER); + mtk::draw_separator(c, x, y, c.w - 2 * x, theme); y += 16; - // UTC Offset - c.text(x, y + 6, "UTC Offset", colors::TEXT_COLOR); - bx = x + 180; + c.text(x, y + 6, "UTC Offset", theme.text); + Rect minus_btn = {bx, y, 36, btn_h}; + Rect plus_btn = {bx + 36 + 90, y, 36, btn_h}; + mtk::draw_button(c, minus_btn, "-", mtk::BUTTON_SECONDARY, mtk::widget_state(), theme); + mtk::draw_button(c, plus_btn, "+", mtk::BUTTON_SECONDARY, mtk::widget_state(), theme); - // [-] button - draw_toggle_btn(c, bx, y, 36, btn_h, "-", false, accent); - - // Offset label int off = s.tz_offset_minutes; - int offH = off / 60; - int offM = off % 60; - if (offM < 0) offM = -offM; + int off_h = off / 60; + int off_m = off % 60; + if (off_m < 0) off_m = -off_m; + char tz_label[16]; - if (offM) - snprintf(tz_label, sizeof(tz_label), "UTC%s%d:%02d", offH >= 0 ? "+" : "", offH, offM); - else - snprintf(tz_label, sizeof(tz_label), "UTC%s%d", offH >= 0 ? "+" : "", offH); + if (off_m) { + snprintf(tz_label, sizeof(tz_label), "UTC%s%d:%02d", off_h >= 0 ? "+" : "", off_h, off_m); + } else { + snprintf(tz_label, sizeof(tz_label), "UTC%s%d", off_h >= 0 ? "+" : "", off_h); + } + int tw = text_width(tz_label); int label_w = 90; - c.text(bx + 36 + (label_w - tw) / 2, y + 6, tz_label, colors::TEXT_COLOR); - - // [+] button - draw_toggle_btn(c, bx + 36 + label_w, y, 36, btn_h, "+", false, accent); + c.text(bx + 36 + (label_w - tw) / 2, y + 6, tz_label, theme.text); } bool settings_handle_display_click(SettingsState* st, int mx, int cy) { @@ -92,54 +89,53 @@ bool settings_handle_display_click(SettingsState* st, int mx, int cy) { int btn_h = 28; int bx = x + 180; - // Window Shadows: On - if (mx >= bx && mx < bx + btn_w && cy >= y && cy < y + btn_h) { + Rect shadows_on = {bx, y, btn_w, btn_h}; + Rect shadows_off = {bx + btn_w + 8, y, btn_w, btn_h}; + if (shadows_on.contains(mx, cy)) { s.show_shadows = true; settings_persist(st); return true; } - // Window Shadows: Off - if (mx >= bx + btn_w + 8 && mx < bx + btn_w * 2 + 8 && cy >= y && cy < y + btn_h) { + if (shadows_off.contains(mx, cy)) { s.show_shadows = false; settings_persist(st); return true; } y += btn_h + 20 + 16; - // Clock: 24h - if (mx >= bx && mx < bx + btn_w && cy >= y && cy < y + btn_h) { + Rect clock_24 = {bx, y, btn_w, btn_h}; + Rect clock_12 = {bx + btn_w + 8, y, btn_w, btn_h}; + if (clock_24.contains(mx, cy)) { s.clock_24h = true; settings_persist(st); return true; } - // Clock: 12h - if (mx >= bx + btn_w + 8 && mx < bx + btn_w * 2 + 8 && cy >= y && cy < y + btn_h) { + if (clock_12.contains(mx, cy)) { s.clock_24h = false; settings_persist(st); return true; } y += btn_h + 20 + 16; - // UI Scale buttons int sbw = 68; - // Small - if (mx >= bx && mx < bx + sbw && cy >= y && cy < y + btn_h) { + Rect scale_small = {bx, y, sbw, btn_h}; + Rect scale_default = {bx + sbw + 8, y, sbw, btn_h}; + Rect scale_large = {bx + (sbw + 8) * 2, y, sbw, btn_h}; + if (scale_small.contains(mx, cy)) { s.ui_scale = 0; apply_ui_scale(0); montauk::win_setscale(0); settings_persist(st); return true; } - // Default - if (mx >= bx + sbw + 8 && mx < bx + sbw * 2 + 8 && cy >= y && cy < y + btn_h) { + if (scale_default.contains(mx, cy)) { s.ui_scale = 1; apply_ui_scale(1); montauk::win_setscale(1); settings_persist(st); return true; } - // Large - if (mx >= bx + (sbw + 8) * 2 && mx < bx + sbw * 3 + 16 && cy >= y && cy < y + btn_h) { + if (scale_large.contains(mx, cy)) { s.ui_scale = 2; apply_ui_scale(2); montauk::win_setscale(2); @@ -148,8 +144,9 @@ bool settings_handle_display_click(SettingsState* st, int mx, int cy) { } y += btn_h + 20 + 16; - // UTC Offset: [-] button - if (mx >= bx && mx < bx + 36 && cy >= y && cy < y + btn_h) { + Rect minus_btn = {bx, y, 36, btn_h}; + Rect plus_btn = {bx + 36 + 90, y, 36, btn_h}; + if (minus_btn.contains(mx, cy)) { if (s.tz_offset_minutes > -720) { s.tz_offset_minutes -= 60; montauk::settz(s.tz_offset_minutes); @@ -157,9 +154,7 @@ bool settings_handle_display_click(SettingsState* st, int mx, int cy) { } return true; } - // UTC Offset: [+] button - int label_w = 90; - if (mx >= bx + 36 + label_w && mx < bx + 36 + label_w + 36 && cy >= y && cy < y + btn_h) { + if (plus_btn.contains(mx, cy)) { if (s.tz_offset_minutes < 840) { s.tz_offset_minutes += 60; montauk::settz(s.tz_offset_minutes); diff --git a/programs/src/desktop/apps/settings/helpers.cpp b/programs/src/desktop/apps/settings/helpers.cpp index 46b5614..e08b5f6 100644 --- a/programs/src/desktop/apps/settings/helpers.cpp +++ b/programs/src/desktop/apps/settings/helpers.cpp @@ -29,52 +29,4 @@ int find_swatch(const Color* palette, Color current) { return -1; } -void draw_swatch_row(Canvas& c, int x, int y, const Color* palette, Color selected, Color accent) { - int sel = find_swatch(palette, selected); - for (int i = 0; i < SWATCH_COUNT; i++) { - int sx = x + i * (SWATCH_SIZE + SWATCH_GAP); - // Draw selection border - if (i == sel) { - c.fill_rounded_rect(sx - 2, y - 2, SWATCH_SIZE + 4, SWATCH_SIZE + 4, 4, accent); - } - c.fill_rounded_rect(sx, y, SWATCH_SIZE, SWATCH_SIZE, 3, palette[i]); - // Thin border for light colors - c.rect(sx, y, SWATCH_SIZE, SWATCH_SIZE, Color::from_rgb(0xCC, 0xCC, 0xCC)); - } -} - -void draw_radio(Canvas& c, int x, int y, bool selected, Color accent) { - int r = 7; - // Outer circle (simple square approximation with rounded rect) - c.fill_rounded_rect(x, y, r * 2, r * 2, r, Color::from_rgb(0xCC, 0xCC, 0xCC)); - c.fill_rounded_rect(x + 1, y + 1, r * 2 - 2, r * 2 - 2, r - 1, colors::WHITE); - if (selected) { - c.fill_rounded_rect(x + 4, y + 4, r * 2 - 8, r * 2 - 8, r - 4, accent); - } -} - -void draw_toggle_btn(Canvas& c, int x, int y, int bw, int bh, - const char* label, bool active, Color accent) { - Color bg = active ? accent : colors::WINDOW_BG; - Color fg = active ? colors::WHITE : colors::TEXT_COLOR; - c.fill_rounded_rect(x, y, bw, bh, 4, bg); - if (!active) { - c.rect(x, y, bw, bh, colors::BORDER); - } - int tw = text_width(label); - int fh = system_font_height(); - c.text(x + (bw - tw) / 2, y + (bh - fh) / 2, label, fg); -} - -bool swatch_hit(int mx, int my, int row_x, int row_y, int* out_idx) { - for (int i = 0; i < SWATCH_COUNT; i++) { - int sx = row_x + i * (SWATCH_SIZE + SWATCH_GAP); - if (mx >= sx && mx < sx + SWATCH_SIZE && my >= row_y && my < row_y + SWATCH_SIZE) { - *out_idx = i; - return true; - } - } - return false; -} - } // namespace settings_app diff --git a/programs/src/desktop/apps/settings/settings_internal.hpp b/programs/src/desktop/apps/settings/settings_internal.hpp index d542c12..1d24dd6 100644 --- a/programs/src/desktop/apps/settings/settings_internal.hpp +++ b/programs/src/desktop/apps/settings/settings_internal.hpp @@ -8,6 +8,7 @@ #include "../apps_common.hpp" #include "../../wallpaper.hpp" +#include #include #include @@ -80,17 +81,15 @@ inline constexpr int USER_BTN_H = 30; inline constexpr int USER_BTN_W = 100; inline constexpr int USER_FIELD_H = 32; +inline mtk::Theme settings_theme(const SettingsState* st) { + return mtk::make_theme(st->desktop->settings.accent_color); +} + bool color_eq(Color a, Color b); bool settings_status_visible(SettingsState* st); void settings_set_status(SettingsState* st, const char* msg); int find_swatch(const Color* palette, Color current); -void draw_swatch_row(Canvas& c, int x, int y, const Color* palette, Color selected, Color accent); -void draw_radio(Canvas& c, int x, int y, bool selected, Color accent); -void draw_toggle_btn(Canvas& c, int x, int y, int bw, int bh, - const char* label, bool active, Color accent); -bool swatch_hit(int mx, int my, int row_x, int row_y, int* out_idx); - void apply_ui_scale(int scale); void settings_draw_appearance(Canvas& c, SettingsState* st); diff --git a/programs/src/desktop/apps/settings/users.cpp b/programs/src/desktop/apps/settings/users.cpp index fd4ad8a..4b770ab 100644 --- a/programs/src/desktop/apps/settings/users.cpp +++ b/programs/src/desktop/apps/settings/users.cpp @@ -14,15 +14,13 @@ void users_reload(SettingsState* st) { } int user_row_height() { - return system_font_height() * 2 + 12; // Two lines of text + padding + return system_font_height() * 2 + 12; } void settings_draw_users(Canvas& c, SettingsState* st) { if (!st->users_loaded) users_reload(st); - Color accent = st->desktop->settings.accent_color; - Color dim = Color::from_rgb(0x88, 0x88, 0x88); - Color card_bg = Color::from_rgb(0xF8, 0xF8, 0xF8); + mtk::Theme theme = settings_theme(st); int x = 16; int y = 20; int sfh = system_font_height(); @@ -30,105 +28,73 @@ void settings_draw_users(Canvas& c, SettingsState* st) { int row_h = user_row_height(); if (!st->desktop->is_admin) { - c.text(x, y, "Admin access required to manage users.", dim); + c.text(x, y, "Admin access required to manage users.", theme.text_muted); return; } - // User list for (int i = 0; i < st->user_count; i++) { - bool sel = (i == st->selected_user); + bool selected = (i == st->selected_user); bool is_current = montauk::streq(st->users[i].username, st->desktop->current_user); + Rect row = {x, y, content_w, row_h}; + mtk::draw_list_row(c, row, selected, (i % 2) == 0, theme); - if (sel) { - c.fill_rounded_rect(x, y, content_w, row_h, 4, accent); - } else if (i % 2 == 0) { - c.fill_rounded_rect(x, y, content_w, row_h, 4, card_bg); - } + Color text_color = selected ? theme.text_inverse : theme.text; + Color sub_color = selected ? Color::from_rgb(0xE3, 0xEA, 0xFF) : theme.text_muted; - Color tc = sel ? colors::WHITE : colors::TEXT_COLOR; - Color sc = sel ? Color::from_rgb(0xDD, 0xDD, 0xFF) : dim; - - // Display name (primary line) int text_y = y + 4; - c.text(x + 12, text_y, st->users[i].display_name, tc); + c.text(x + 12, text_y, st->users[i].display_name, text_color); - // Username (secondary line) char sub[48]; if (is_current) { snprintf(sub, sizeof(sub), "@%s (you)", st->users[i].username); } else { snprintf(sub, sizeof(sub), "@%s", st->users[i].username); } - c.text(x + 12, text_y + sfh + 2, sub, sc); + c.text(x + 12, text_y + sfh + 2, sub, sub_color); - // Role badge const char* role = st->users[i].role; - int rw = text_width(role) + 12; - int badge_x = c.w - x - rw - 8; - int badge_y = y + (row_h - sfh - 4) / 2; - if (sel) { - c.fill_rounded_rect(badge_x, badge_y, rw, sfh + 4, (sfh + 4) / 2, - Color::from_rgb(0xFF, 0xFF, 0xFF)); - c.text(badge_x + 6, badge_y + 2, role, accent); + int badge_w = mtk::badge_width(role, theme); + int badge_h = mtk::badge_height(); + int badge_x = c.w - x - badge_w - 8; + int badge_y = y + (row_h - badge_h) / 2; + if (selected) { + mtk::draw_badge(c, badge_x, badge_y, role, colors::WHITE, theme.accent, theme); + } else if (montauk::streq(role, "admin")) { + mtk::draw_badge(c, badge_x, badge_y, role, + theme.badge_admin_bg, theme.badge_admin_fg, theme); } else { - bool is_admin_role = montauk::streq(role, "admin"); - Color badge_bg = is_admin_role - ? Color::from_rgb(0xE8, 0xD8, 0xF0) - : Color::from_rgb(0xE0, 0xE8, 0xF0); - Color badge_fg = is_admin_role - ? Color::from_rgb(0x7B, 0x3E, 0xB8) - : Color::from_rgb(0x36, 0x7B, 0xF0); - c.fill_rounded_rect(badge_x, badge_y, rw, sfh + 4, (sfh + 4) / 2, badge_bg); - c.text(badge_x + 6, badge_y + 2, role, badge_fg); + mtk::draw_badge(c, badge_x, badge_y, role, + theme.badge_user_bg, theme.badge_user_fg, theme); } y += row_h + 4; } - // Separator y += 8; - c.hline(x, y, content_w, colors::BORDER); + mtk::draw_separator(c, x, y, content_w, theme); y += 16; - // Status message if (settings_status_visible(st)) { - c.text(x, y, st->status_msg, accent); + c.text(x, y, st->status_msg, theme.accent); y += sfh + 8; } - // Action buttons - int btn_x = x; + Rect add_btn = {x, y, 90, USER_BTN_H}; + Rect change_btn = {x + 98, y, USER_BTN_W, USER_BTN_H}; + Rect delete_btn = {x + 98 + USER_BTN_W + 8, y, 70, USER_BTN_H}; + bool has_selection = st->selected_user >= 0; - // Add User - c.fill_rounded_rect(btn_x, y, 90, USER_BTN_H, 4, accent); - { - int tw = text_width("Add User"); - c.text(btn_x + (90 - tw) / 2, y + (USER_BTN_H - sfh) / 2, "Add User", colors::WHITE); - } - btn_x += 98; - - // Change Password (disabled when no selection) - { - bool enabled = st->selected_user >= 0; - Color bg = enabled ? accent : Color::from_rgb(0xCC, 0xCC, 0xCC); - c.fill_rounded_rect(btn_x, y, USER_BTN_W, USER_BTN_H, 4, bg); - int tw = text_width("Change Pwd"); - c.text(btn_x + (USER_BTN_W - tw) / 2, y + (USER_BTN_H - sfh) / 2, "Change Pwd", colors::WHITE); - } - btn_x += USER_BTN_W + 8; - - // Delete (disabled when no selection) - { - bool enabled = st->selected_user >= 0; - Color del_bg = enabled ? Color::from_rgb(0xD0, 0x3E, 0x3E) : Color::from_rgb(0xCC, 0xCC, 0xCC); - c.fill_rounded_rect(btn_x, y, 70, USER_BTN_H, 4, del_bg); - int tw = text_width("Delete"); - c.text(btn_x + (70 - tw) / 2, y + (USER_BTN_H - sfh) / 2, "Delete", colors::WHITE); - } + mtk::draw_button(c, add_btn, "Add User", mtk::BUTTON_PRIMARY, + mtk::widget_state(), theme); + mtk::draw_button(c, change_btn, "Change Pwd", mtk::BUTTON_SECONDARY, + mtk::widget_state(false, false, has_selection), theme); + mtk::draw_button(c, delete_btn, "Delete", mtk::BUTTON_DANGER, + mtk::widget_state(false, false, has_selection), theme); } bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy) { if (!st->desktop->is_admin) return false; + if (!st->users_loaded) users_reload(st); int x = 16; int sfh = system_font_height(); @@ -136,42 +102,32 @@ bool settings_handle_users_click(Window* win, SettingsState* st, int mx, int cy) int content_w = win->content_w - 2 * x; int row_h = user_row_height(); - // User list rows for (int i = 0; i < st->user_count; i++) { - if (cy >= y && cy < y + row_h && mx >= x && mx < x + content_w) { + Rect row = {x, y, content_w, row_h}; + if (row.contains(mx, cy)) { st->selected_user = (st->selected_user == i) ? -1 : i; return true; } y += row_h + 4; } - // Separator + gap - y += 8 + 1 + 16; - - // Status message (if shown, takes space) + y += 8 + 16; if (settings_status_visible(st)) { y += sfh + 8; } - // Action buttons - int btn_x = x; - - // Add User - if (mx >= btn_x && mx < btn_x + 90 && cy >= y && cy < y + USER_BTN_H) { + Rect add_btn = {x, y, 90, USER_BTN_H}; + Rect change_btn = {x + 98, y, USER_BTN_W, USER_BTN_H}; + Rect delete_btn = {x + 98 + USER_BTN_W + 8, y, 70, USER_BTN_H}; + if (add_btn.contains(mx, cy)) { open_add_user_dialog(st); return true; } - btn_x += 98; - - // Change Password - if (mx >= btn_x && mx < btn_x + USER_BTN_W && cy >= y && cy < y + USER_BTN_H) { + if (change_btn.contains(mx, cy)) { if (st->selected_user >= 0) open_change_pwd_dialog(st); return true; } - btn_x += USER_BTN_W + 8; - - // Delete - if (mx >= btn_x && mx < btn_x + 70 && cy >= y && cy < y + USER_BTN_H) { + if (delete_btn.contains(mx, cy)) { if (st->selected_user >= 0) open_delete_user_dialog(st); return true; }