feat: standardize gui toolkit

This commit is contained in:
2026-04-10 22:33:18 +02:00
parent aa815122be
commit 91b09e8194
8 changed files with 373 additions and 487 deletions
+8 -9
View File
@@ -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
+11 -34
View File
@@ -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) {
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;
}
@@ -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_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) {
@@ -128,25 +122,24 @@ bool settings_handle_appearance_click(Window* win, SettingsState* st, int mx, in
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;
+189 -165
View File
@@ -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);
+59 -64
View File
@@ -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);
@@ -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
@@ -8,6 +8,7 @@
#include "../apps_common.hpp"
#include "../../wallpaper.hpp"
#include <gui/mtk.hpp>
#include <montauk/config.h>
#include <montauk/user.h>
@@ -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);
+43 -87
View File
@@ -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;
}