From 825777e7a7d9d95744d80845ee009ceaae2440c0 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Wed, 15 Apr 2026 18:51:42 +0200 Subject: [PATCH] feat: port Disks app to MTK --- programs/src/disks/disks.h | 77 +++++- programs/src/disks/main.cpp | 176 ++++++------ programs/src/disks/render.cpp | 499 ++++++++++++++-------------------- 3 files changed, 378 insertions(+), 374 deletions(-) diff --git a/programs/src/disks/disks.h b/programs/src/disks/disks.h index 5f3976b..1d75d7d 100644 --- a/programs/src/disks/disks.h +++ b/programs/src/disks/disks.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include extern "C" { #include @@ -33,7 +33,6 @@ static constexpr int MAP_PAD = 16; static constexpr int MAX_PARTS = 32; static constexpr int MAX_DISKS = 8; static constexpr int STATUS_H = 26; -static constexpr int FONT_SIZE = 16; static constexpr int TB_BTN_Y = 7; static constexpr int TB_BTN_H = 26; @@ -120,7 +119,6 @@ struct DiskToolState { extern int g_win_w, g_win_h; extern DiskToolState g_state; -extern TrueTypeFont* g_font; // ============================================================================ // Partition colors @@ -128,6 +126,79 @@ extern TrueTypeFont* g_font; extern const Color part_colors[NUM_PART_COLORS]; +// ============================================================================ +// Shared layout helpers +// ============================================================================ + +inline int disk_button_width(int idx) { + char label[8]; + snprintf(label, sizeof(label), "Disk %d", idx); + return text_width(label) + 16; +} + +inline Rect toolbar_disk_button_rect(int idx) { + int bx = 8; + for (int i = 0; i < idx; i++) + bx += disk_button_width(i) + 6; + return {bx, TB_BTN_Y, disk_button_width(idx), TB_BTN_H}; +} + +inline Rect toolbar_refresh_button_rect() { + return {g_win_w - 8 - 64, TB_BTN_Y, 64, TB_BTN_H}; +} + +inline Rect toolbar_mount_button_rect() { + Rect refresh = toolbar_refresh_button_rect(); + return {refresh.x - 6 - 60, TB_BTN_Y, 60, TB_BTN_H}; +} + +inline Rect toolbar_format_button_rect() { + Rect mount = toolbar_mount_button_rect(); + return {mount.x - 6 - 64, TB_BTN_Y, 64, TB_BTN_H}; +} + +inline Rect toolbar_newpart_button_rect() { + Rect format = toolbar_format_button_rect(); + return {format.x - 6 - 74, TB_BTN_Y, 74, TB_BTN_H}; +} + +inline int content_title_y() { + return TOOLBAR_H + 8; +} + +inline int partition_map_y() { + return content_title_y() + system_font_height() + 8; +} + +inline int partition_header_y() { + return partition_map_y() + MAP_H + 8; +} + +inline int partition_list_y() { + return partition_header_y() + HEADER_H; +} + +inline Rect format_option_rect(int dialog_w, int index) { + int fh = system_font_height(); + int sel_y = 12 + fh * 2 + 18; + int opt_y = sel_y + fh + 8; + return {24, opt_y + index * 32, dialog_w - 48, 28}; +} + +inline Rect dialog_primary_button_rect(int dialog_w, int dialog_h) { + int btn_w = 90; + int btn_h = 30; + int gap = 16; + int total_w = btn_w * 2 + gap; + int bx = (dialog_w - total_w) / 2; + return {bx, dialog_h - btn_h - 16, btn_w, btn_h}; +} + +inline Rect dialog_secondary_button_rect(int dialog_w, int dialog_h) { + Rect primary = dialog_primary_button_rect(dialog_w, dialog_h); + return {primary.x + primary.w + 16, primary.y, primary.w, primary.h}; +} + // ============================================================================ // Function declarations — helpers (main.cpp) // ============================================================================ diff --git a/programs/src/disks/main.cpp b/programs/src/disks/main.cpp index 160ef20..259fe9a 100644 --- a/programs/src/disks/main.cpp +++ b/programs/src/disks/main.cpp @@ -15,7 +15,6 @@ int g_win_w = INIT_W; int g_win_h = INIT_H; DiskToolState g_state; -TrueTypeFont* g_font = nullptr; const Color part_colors[NUM_PART_COLORS] = { Color::from_rgb(0x42, 0x7A, 0xB5), @@ -66,61 +65,44 @@ void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorS } // ============================================================================ -// Toolbar hit testing (returns button widths matching render layout) +// Toolbar hit testing // ============================================================================ -static int disk_btn_width(int idx) { - char label[8]; - snprintf(label, sizeof(label), "Disk %d", idx); - int lw = g_font ? g_font->measure_text(label, FONT_SIZE) + 16 : 48; - return lw; -} - static bool handle_toolbar_click(int mx, int my) { if (my >= TOOLBAR_H || my < TB_BTN_Y) return false; auto& dt = g_state; + if (dt.disk_count == 0) return false; + bool has_sel = dt.selected_part >= 0; // Disk selector buttons - int bx = 8; for (int i = 0; i < dt.disk_count; i++) { - int lw = disk_btn_width(i); - if (mx >= bx && mx < bx + lw && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) { + if (toolbar_disk_button_rect(i).contains(mx, my)) { dt.selected_disk = i; dt.selected_part = -1; dt.scroll_y = 0; return true; } - bx += lw + 6; } // Right-side buttons (must match render layout) - int rx = g_win_w - 8; - - int ref_w = 64; rx -= ref_w; - if (mx >= rx && mx < rx + ref_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) { + if (toolbar_refresh_button_rect().contains(mx, my)) { disktool_refresh(); set_status("Refreshed"); return true; } - rx -= 6; - int mnt_w = 60; rx -= mnt_w; - if (mx >= rx && mx < rx + mnt_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) { + if (has_sel && toolbar_mount_button_rect().contains(mx, my)) { do_mount_partition(); return true; } - rx -= 6; - int fmt_w = 64; rx -= fmt_w; - if (mx >= rx && mx < rx + fmt_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) { + if (has_sel && toolbar_format_button_rect().contains(mx, my)) { open_format_dialog(); return true; } - rx -= 6; - int np_w = 74; rx -= np_w; - if (mx >= rx && mx < rx + np_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) { + if (toolbar_newpart_button_rect().contains(mx, my)) { do_create_partition(); return true; } @@ -136,8 +118,7 @@ static bool handle_content_click(int mx, int my) { auto& dt = g_state; if (dt.disk_count == 0 || dt.selected_disk < 0) return false; - int fh = g_font ? g_font->get_cache(FONT_SIZE)->ascent - g_font->get_cache(FONT_SIZE)->descent : 16; - int y = TOOLBAR_H + 8 + fh + 8; + int y = partition_map_y(); // Partition map bar click int map_x = MAP_PAD; @@ -163,12 +144,9 @@ static bool handle_content_click(int mx, int my) { return true; } - y += MAP_H + 8 + HEADER_H; - // Partition list click - int list_bottom = g_win_h - STATUS_H; - if (my >= y && my < list_bottom) { - int row = (my - y + dt.scroll_y) / ITEM_H; + if (my >= partition_list_y() && my < g_win_h - STATUS_H) { + int row = (my - partition_list_y() + dt.scroll_y) / ITEM_H; int part_indices[MAX_PARTS]; int nparts = get_disk_parts(part_indices, MAX_PARTS); if (row >= 0 && row < nparts) @@ -189,16 +167,10 @@ static bool handle_newpart_dialog_click(int mx, int my, bool clicked) { auto& dlg = g_state.np_dlg; if (!dlg.open) return false; - int dw = NP_DLG_W, dh = NP_DLG_H; - - int btn_w = 90, btn_h = 30; - int btn_y = dh - btn_h - 16; - int gap = 16; - int total_w = btn_w * 2 + gap; - int bx = (dw - total_w) / 2; - - dlg.hover_confirm = (mx >= bx && mx < bx + btn_w && my >= btn_y && my < btn_y + btn_h); - dlg.hover_cancel = (mx >= bx + btn_w + gap && mx < bx + btn_w * 2 + gap && my >= btn_y && my < btn_y + btn_h); + Rect confirm = dialog_primary_button_rect(NP_DLG_W, NP_DLG_H); + Rect cancel = dialog_secondary_button_rect(NP_DLG_W, NP_DLG_H); + dlg.hover_confirm = confirm.contains(mx, my); + dlg.hover_cancel = cancel.contains(mx, my); if (!clicked) return true; @@ -223,27 +195,17 @@ static bool handle_format_dialog_click(int mx, int my, bool clicked) { auto& dlg = g_state.fmt_dlg; if (!dlg.open) return false; - int dw = FMT_DLG_W, dh = FMT_DLG_H; - int fh = g_font ? g_font->get_cache(FONT_SIZE)->ascent - g_font->get_cache(FONT_SIZE)->descent : 16; - // Button hit testing - int btn_w = 90, btn_h = 30; - int btn_y = dh - btn_h - 16; - int gap = 16; - int total_w = btn_w * 2 + gap; - int bx = (dw - total_w) / 2; - - dlg.hover_format = (mx >= bx && mx < bx + btn_w && my >= btn_y && my < btn_y + btn_h); - dlg.hover_cancel = (mx >= bx + btn_w + gap && mx < bx + btn_w * 2 + gap && my >= btn_y && my < btn_y + btn_h); + Rect format_btn = dialog_primary_button_rect(FMT_DLG_W, FMT_DLG_H); + Rect cancel_btn = dialog_secondary_button_rect(FMT_DLG_W, FMT_DLG_H); + dlg.hover_format = format_btn.contains(mx, my); + dlg.hover_cancel = cancel_btn.contains(mx, my); if (!clicked) return true; // FS type selector - int sel_y = 12 + fh * 2 + 18; - int opt_y = sel_y + fh + 8; for (int i = 0; i < NUM_FS_TYPES; i++) { - int iy = opt_y + i * 32; - if (mx >= 24 && mx < dw - 24 && my >= iy && my < iy + 28) { + if (format_option_rect(FMT_DLG_W, i).contains(mx, my)) { dlg.selected_fs = i; return true; } @@ -262,6 +224,74 @@ static bool handle_format_dialog_click(int mx, int my, bool clicked) { return true; } +static int hovered_disk_button(int mx, int my) { + for (int i = 0; i < g_state.disk_count; i++) { + if (toolbar_disk_button_rect(i).contains(mx, my)) + return i; + } + return -1; +} + +static int hovered_partition_row(int mx, int my) { + if (g_state.disk_count == 0 || g_state.selected_disk < 0) return -1; + if (my < partition_list_y() || my >= g_win_h - STATUS_H) return -1; + + int part_indices[MAX_PARTS]; + int nparts = get_disk_parts(part_indices, MAX_PARTS); + int row = (my - partition_list_y() + g_state.scroll_y) / ITEM_H; + if (row < 0 || row >= nparts) return -1; + return row; +} + +static int max_scroll_y() { + int part_indices[MAX_PARTS]; + int nparts = get_disk_parts(part_indices, MAX_PARTS); + int visible_h = g_win_h - STATUS_H - partition_list_y(); + int total_h = nparts * ITEM_H; + if (total_h <= visible_h) return 0; + return total_h - visible_h; +} + +static void clamp_scroll() { + g_state.scroll_y = gui_clamp(g_state.scroll_y, 0, max_scroll_y()); +} + +static bool handle_mouse(const Montauk::WinEvent& ev) { + int prev_x = g_state.mouse_x; + int prev_y = g_state.mouse_y; + int prev_disk_hover = hovered_disk_button(prev_x, prev_y); + int prev_part_hover = hovered_partition_row(prev_x, prev_y); + bool prev_refresh_hover = toolbar_refresh_button_rect().contains(prev_x, prev_y); + bool prev_mount_hover = toolbar_mount_button_rect().contains(prev_x, prev_y); + bool prev_format_hover = toolbar_format_button_rect().contains(prev_x, prev_y); + bool prev_newpart_hover = toolbar_newpart_button_rect().contains(prev_x, prev_y); + + g_state.mouse_x = ev.mouse.x; + g_state.mouse_y = ev.mouse.y; + + bool redraw = false; + redraw |= (prev_disk_hover != hovered_disk_button(g_state.mouse_x, g_state.mouse_y)); + redraw |= (prev_part_hover != hovered_partition_row(g_state.mouse_x, g_state.mouse_y)); + redraw |= (prev_refresh_hover != toolbar_refresh_button_rect().contains(g_state.mouse_x, g_state.mouse_y)); + redraw |= (prev_mount_hover != toolbar_mount_button_rect().contains(g_state.mouse_x, g_state.mouse_y)); + redraw |= (prev_format_hover != toolbar_format_button_rect().contains(g_state.mouse_x, g_state.mouse_y)); + redraw |= (prev_newpart_hover != toolbar_newpart_button_rect().contains(g_state.mouse_x, g_state.mouse_y)); + + if (ev.mouse.scroll != 0) { + g_state.scroll_y -= ev.mouse.scroll * 20; + clamp_scroll(); + return true; + } + + bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); + if (clicked) { + if (handle_toolbar_click(ev.mouse.x, ev.mouse.y) || handle_content_click(ev.mouse.x, ev.mouse.y)) + return true; + } + + return redraw; +} + // ============================================================================ // Key handling // ============================================================================ @@ -329,16 +359,11 @@ extern "C" void _start() { montauk::memset(&g_state, 0, sizeof(g_state)); g_state.selected_disk = 0; g_state.selected_part = -1; + g_state.mouse_x = -1; + g_state.mouse_y = -1; - // Load font - { - TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont)); - if (f) { - montauk::memset(f, 0, sizeof(TrueTypeFont)); - if (!f->init("0:/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; } - } - g_font = f; - } + if (!fonts::init()) + montauk::exit(1); disktool_refresh(); @@ -418,6 +443,7 @@ extern "C" void _start() { g_win_w = win.width; g_win_h = win.height; pixels = win.pixels; + clamp_scroll(); redraw_main = true; } @@ -430,21 +456,7 @@ extern "C" void _start() { // Mouse if (ev.type == 1) { - int mx = ev.mouse.x; - int my = ev.mouse.y; - bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1); - - // Scroll - if (ev.mouse.scroll != 0) { - g_state.scroll_y -= ev.mouse.scroll * 20; - if (g_state.scroll_y < 0) g_state.scroll_y = 0; - redraw_main = true; - } - - if (clicked) { - if (handle_toolbar_click(mx, my) || handle_content_click(mx, my)) - redraw_main = true; - } + redraw_main |= handle_mouse(ev); } if (redraw_main) { render(pixels); win.present(); } diff --git a/programs/src/disks/render.cpp b/programs/src/disks/render.cpp index 343e388..0b63ee8 100644 --- a/programs/src/disks/render.cpp +++ b/programs/src/disks/render.cpp @@ -1,184 +1,146 @@ /* * render.cpp - * MontaukOS Disk Tool — rendering + * MontaukOS Disk Tool - rendering * Copyright (c) 2026 Daniel Hammer */ #include "disks.h" +#include +#include -// ============================================================================ -// Pixel helpers -// ============================================================================ - -static void px_fill(uint32_t* px, int bw, int bh, - int x, int y, int w, int h, Color c) { - uint32_t v = c.to_pixel(); - int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y; - int x1 = x + w > bw ? bw : x + w; - int y1 = y + h > bh ? bh : y + h; - for (int row = y0; row < y1; row++) - for (int col = x0; col < x1; col++) - px[row * bw + col] = v; +static mtk::Theme disks_theme() { + mtk::Theme theme = mtk::make_theme(); + theme.window_bg = BG_COLOR; + theme.surface = TOOLBAR_BG; + theme.surface_alt = HEADER_BG; + theme.surface_hover = HOVER_BG; + theme.border = BORDER_COLOR; + theme.text = TEXT_COLOR; + theme.text_muted = FAINT_TEXT; + theme.text_subtle = DIM_TEXT; + theme.selection = HOVER_BG; + theme.accent_soft = HOVER_BG; + theme.disabled_bg = Color::from_rgb(0xF0, 0xF0, 0xF0); + theme.disabled_fg = Color::from_rgb(0x9A, 0x9A, 0x9A); + return theme; } -static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c) { - if (y < 0 || y >= bh) return; - uint32_t v = c.to_pixel(); - int x0 = x < 0 ? 0 : x; - int x1 = x + w > bw ? bw : x + w; - for (int col = x0; col < x1; col++) - px[y * bw + col] = v; +static bool mouse_in_rect(const Rect& rect) { + return rect.contains(g_state.mouse_x, g_state.mouse_y); } -static void px_fill_rounded(uint32_t* px, int bw, int bh, - int x, int y, int w, int h, int r, Color c) { - uint32_t v = c.to_pixel(); - for (int row = 0; row < h; row++) { - int dy = y + row; - if (dy < 0 || dy >= bh) continue; - for (int col = 0; col < w; col++) { - int dx = x + col; - if (dx < 0 || dx >= bw) continue; - bool skip = false; - int cx, cy; - if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; } - else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; } - else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; } - else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; } - if (!skip) px[dy * bw + dx] = v; - } - } +static mtk::WidgetState toolbar_button_state(const Rect& rect, bool enabled, bool active = false) { + return mtk::widget_state(active, mouse_in_rect(rect), enabled); } -static void px_rect(uint32_t* px, int bw, int bh, - int x, int y, int w, int h, Color c) { - px_hline(px, bw, bh, x, y, w, c); - px_hline(px, bw, bh, x, y + h - 1, w, c); - for (int row = y; row < y + h; row++) { - if (row < 0 || row >= bh) continue; - if (x >= 0 && x < bw) px[row * bw + x] = c.to_pixel(); - int rx = x + w - 1; - if (rx >= 0 && rx < bw) px[row * bw + rx] = c.to_pixel(); - } -} - -static void px_text(uint32_t* px, int bw, int bh, - int x, int y, const char* text, Color c) { - if (g_font) - g_font->draw_to_buffer(px, bw, bh, x, y, text, c, FONT_SIZE); -} - -static int text_w(const char* text) { - return g_font ? g_font->measure_text(text, FONT_SIZE) : 0; -} - -static int font_h() { - if (!g_font) return 16; - auto* cache = g_font->get_cache(FONT_SIZE); - return cache->ascent - cache->descent; -} - -// ============================================================================ -// Button helper -// ============================================================================ - -static void px_button(uint32_t* px, int bw, int bh, - int x, int y, int w, int h, - const char* label, bool enabled, bool hovered, bool primary) { - Color bg, fg; - if (!enabled) { - bg = Color::from_rgb(0xF0, 0xF0, 0xF0); - fg = Color::from_rgb(0x9A, 0x9A, 0x9A); - } else if (primary) { - bg = hovered ? Color::from_rgb(0x2B, 0x6B, 0xE0) : colors::ACCENT; - fg = WHITE; - } else { - bg = hovered ? Color::from_rgb(0xDC, 0xDC, 0xDC) : Color::from_rgb(0xE8, 0xE8, 0xE8); - fg = TEXT_COLOR; - } - px_fill_rounded(px, bw, bh, x, y, w, h, 6, bg); - int tw = text_w(label); - int fh = font_h(); - px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2, label, fg); -} - -// ============================================================================ -// Render: main view -// ============================================================================ - -static void render_toolbar(uint32_t* px) { - auto& dt = g_state; - int fh = font_h(); - - px_fill(px, g_win_w, g_win_h, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG); - px_hline(px, g_win_w, g_win_h, 0, TOOLBAR_H - 1, g_win_w, BORDER_COLOR); - - int bx = 8; - for (int i = 0; i < dt.disk_count; i++) { - char label[8]; - snprintf(label, sizeof(label), "Disk %d", i); - int lw = text_w(label) + 16; - px_button(px, g_win_w, g_win_h, bx, TB_BTN_Y, lw, TB_BTN_H, label, true, false, false); - bx += lw + 6; - } - - if (dt.disk_count == 0) { - px_text(px, g_win_w, g_win_h, 8, (TOOLBAR_H - fh) / 2, "No disks detected", TEXT_COLOR); +static void draw_text_fit(Canvas& c, int x, int y, + const char* text, int max_w, Color color) { + if (!text || !text[0] || max_w <= 4) return; + if (text_width(text) <= max_w) { + c.text(x, y, text, color); return; } - // Right-side action buttons - bool has_sel = dt.selected_part >= 0; - int rx = g_win_w - 8; + const char* ellipsis = "..."; + int ell_w = text_width(ellipsis); + if (ell_w >= max_w) return; - int ref_w = 64; rx -= ref_w; - px_button(px, g_win_w, g_win_h, rx, TB_BTN_Y, ref_w, TB_BTN_H, - "Refresh", true, false, false); - rx -= 6; + char buf[96]; + int out = 0; + while (text[out] && out < (int)sizeof(buf) - 4) { + buf[out] = text[out]; + buf[out + 1] = '\0'; + if (text_width(buf) + ell_w > max_w) + break; + out++; + } - int mnt_w = 60; rx -= mnt_w; - px_button(px, g_win_w, g_win_h, rx, TB_BTN_Y, mnt_w, TB_BTN_H, - "Mount", has_sel, false, true); - rx -= 6; - - int fmt_w = 64; rx -= fmt_w; - px_button(px, g_win_w, g_win_h, rx, TB_BTN_Y, fmt_w, TB_BTN_H, - "Format", has_sel, false, true); - rx -= 6; - - int np_w = 74; rx -= np_w; - px_button(px, g_win_w, g_win_h, rx, TB_BTN_Y, np_w, TB_BTN_H, - "New Part", true, false, true); + if (out <= 0) return; + buf[out] = '.'; + buf[out + 1] = '.'; + buf[out + 2] = '.'; + buf[out + 3] = '\0'; + c.text(x, y, buf, color); } -static void render_content(uint32_t* px) { +static int hovered_partition_row() { + if (g_state.disk_count == 0 || g_state.selected_disk < 0) return -1; + if (g_state.mouse_y < partition_list_y() || g_state.mouse_y >= g_win_h - STATUS_H) return -1; + + int part_indices[MAX_PARTS]; + int nparts = get_disk_parts(part_indices, MAX_PARTS); + int row = (g_state.mouse_y - partition_list_y() + g_state.scroll_y) / ITEM_H; + if (row < 0 || row >= nparts) return -1; + return row; +} + +static void draw_centered_text(Canvas& c, int width, int y, const char* text, Color color) { + int tw = text_width(text); + c.text((width - tw) / 2, y, text, color); +} + +static void render_toolbar(Canvas& c, const mtk::Theme& theme) { auto& dt = g_state; - int fh = font_h(); + int fh = system_font_height(); + + c.fill_rect(0, 0, g_win_w, TOOLBAR_H, theme.surface); + mtk::draw_separator(c, 0, TOOLBAR_H - 1, g_win_w, theme); + + for (int i = 0; i < dt.disk_count; i++) { + Rect button = toolbar_disk_button_rect(i); + char label[8]; + snprintf(label, sizeof(label), "Disk %d", i); + mtk::draw_button(c, button, label, mtk::BUTTON_SECONDARY, + toolbar_button_state(button, true, i == dt.selected_disk), theme); + } + + if (dt.disk_count == 0) { + c.text(8, (TOOLBAR_H - fh) / 2, "No disks detected", theme.text); + return; + } + + bool has_sel = dt.selected_part >= 0; + Rect newpart = toolbar_newpart_button_rect(); + Rect format = toolbar_format_button_rect(); + Rect mount = toolbar_mount_button_rect(); + Rect refresh = toolbar_refresh_button_rect(); + + mtk::draw_button(c, newpart, "New Part", mtk::BUTTON_PRIMARY, + toolbar_button_state(newpart, true), theme); + mtk::draw_button(c, format, "Format", mtk::BUTTON_TONAL, + toolbar_button_state(format, has_sel), theme); + mtk::draw_button(c, mount, "Mount", mtk::BUTTON_TONAL, + toolbar_button_state(mount, has_sel), theme); + mtk::draw_button(c, refresh, "Refresh", mtk::BUTTON_SECONDARY, + toolbar_button_state(refresh, true), theme); +} + +static void render_content(Canvas& c, const mtk::Theme& theme) { + auto& dt = g_state; + int fh = system_font_height(); if (dt.disk_count == 0 || dt.selected_disk < 0 || dt.selected_disk >= dt.disk_count) return; Montauk::DiskInfo& disk = dt.disks[dt.selected_disk]; - int y = TOOLBAR_H + 8; + int y = content_title_y(); - // Disk model name - px_text(px, g_win_w, g_win_h, MAP_PAD, y, disk.model, TEXT_COLOR); - - // Size and type on the right - char info_str[64], size_str[24]; + char info_str[64]; + char size_str[24]; format_disk_size(size_str, sizeof(size_str), disk.sectorCount, disk.sectorSizeLog); const char* dtype = disk.rpm == 1 ? "SSD" : "HDD"; snprintf(info_str, sizeof(info_str), "%s %s", size_str, dtype); - int iw = text_w(info_str); - px_text(px, g_win_w, g_win_h, g_win_w - iw - MAP_PAD, y, info_str, DIM_TEXT); - y += fh + 8; + int info_w = text_width(info_str); + int info_x = g_win_w - info_w - MAP_PAD; + int model_max_w = info_x - MAP_PAD - 12; + draw_text_fit(c, MAP_PAD, y, disk.model, model_max_w, theme.text); + c.text(info_x, y, info_str, theme.text_subtle); - // Partition map bar - int map_x = MAP_PAD; - int map_w = g_win_w - MAP_PAD * 2; - px_fill_rounded(px, g_win_w, g_win_h, map_x, y, map_w, MAP_H, 6, - Color::from_rgb(0xE4, 0xE4, 0xE4)); + y = partition_map_y(); + + Rect map_rect = {MAP_PAD, y, g_win_w - MAP_PAD * 2, MAP_H}; + c.fill_rounded_rect(map_rect.x, map_rect.y, map_rect.w, map_rect.h, theme.radius_md, theme.surface_alt); int part_indices[MAX_PARTS]; int nparts = get_disk_parts(part_indices, MAX_PARTS); @@ -187,213 +149,172 @@ static void render_content(uint32_t* px) { if (total_sectors > 0 && nparts > 0) { for (int pi = 0; pi < nparts; pi++) { Montauk::PartInfo& p = dt.parts[part_indices[pi]]; - int ppx = map_x + (int)((p.startLba * (uint64_t)map_w) / total_sectors); - int pw = (int)((p.sectorCount * (uint64_t)map_w) / total_sectors); - if (pw < 2) pw = 2; - if (ppx + pw > map_x + map_w) pw = map_x + map_w - ppx; + int part_x = map_rect.x + (int)((p.startLba * (uint64_t)map_rect.w) / total_sectors); + int part_w = (int)((p.sectorCount * (uint64_t)map_rect.w) / total_sectors); + if (part_w < 2) part_w = 2; + if (part_x + part_w > map_rect.x + map_rect.w) + part_w = map_rect.x + map_rect.w - part_x; Color col = part_colors[pi % NUM_PART_COLORS]; if (pi == dt.selected_part) { - col = Color::from_rgb((col.r + 255) / 2, (col.g + 255) / 2, (col.b + 255) / 2); + col = Color::from_rgb((col.r + 255) / 2, + (col.g + 255) / 2, + (col.b + 255) / 2); } - px_fill_rounded(px, g_win_w, g_win_h, ppx, y + 2, pw, MAP_H - 4, 3, col); + c.fill_rounded_rect(part_x, map_rect.y + 2, part_w, MAP_H - 4, theme.radius_sm, col); - char plabel[8]; - snprintf(plabel, sizeof(plabel), "%d", pi); - int plw = text_w(plabel); - if (pw > plw + 4) - px_text(px, g_win_w, g_win_h, ppx + (pw - plw) / 2, y + (MAP_H - fh) / 2, plabel, WHITE); + char label[8]; + snprintf(label, sizeof(label), "%d", pi); + int label_w = text_width(label); + if (part_w > label_w + 4) { + c.text(part_x + (part_w - label_w) / 2, + map_rect.y + (MAP_H - fh) / 2, + label, WHITE); + } } } - y += MAP_H + 8; + y = partition_header_y(); + c.fill_rect(0, y, g_win_w, HEADER_H, theme.surface_alt); + mtk::draw_separator(c, 0, y + HEADER_H - 1, g_win_w, theme); - // Column header - px_fill(px, g_win_w, g_win_h, 0, y, g_win_w, HEADER_H, HEADER_BG); int ty = y + (HEADER_H - fh) / 2; - int col_idx = 12; int col_name = 40; int col_type = g_win_w / 2 - 20; int col_size = g_win_w - 160; int col_lba = g_win_w - 80; - px_text(px, g_win_w, g_win_h, col_idx, ty, "#", DIM_TEXT); - px_text(px, g_win_w, g_win_h, col_name, ty, "Name", DIM_TEXT); - px_text(px, g_win_w, g_win_h, col_type, ty, "Type", DIM_TEXT); - px_text(px, g_win_w, g_win_h, col_size, ty, "Size", DIM_TEXT); - px_text(px, g_win_w, g_win_h, col_lba, ty, "LBA", DIM_TEXT); - px_hline(px, g_win_w, g_win_h, 0, y + HEADER_H - 1, g_win_w, BORDER_COLOR); - y += HEADER_H; + c.text(col_idx, ty, "#", theme.text_subtle); + c.text(col_name, ty, "Name", theme.text_subtle); + c.text(col_type, ty, "Type", theme.text_subtle); + c.text(col_size, ty, "Size", theme.text_subtle); + c.text(col_lba, ty, "LBA", theme.text_subtle); - // Partition rows + int list_y = partition_list_y(); int list_bottom = g_win_h - STATUS_H; - int list_y = y; + int hovered_row = hovered_partition_row(); + for (int pi = 0; pi < nparts; pi++) { int row_y = list_y + pi * ITEM_H - dt.scroll_y; if (row_y + ITEM_H <= list_y) continue; if (row_y >= list_bottom) break; + Rect row_rect = {0, row_y, g_win_w, ITEM_H}; + if (pi == dt.selected_part) { + mtk::draw_list_row(c, row_rect, true, false, theme); + } else if (pi == hovered_row) { + c.fill_rect(row_rect.x, row_rect.y, row_rect.w, row_rect.h, theme.surface_hover); + } else { + mtk::draw_list_row(c, row_rect, false, (pi & 1) != 0, theme); + } + Montauk::PartInfo& p = dt.parts[part_indices[pi]]; - if (pi == dt.selected_part) - px_fill(px, g_win_w, g_win_h, 0, row_y, g_win_w, ITEM_H, HOVER_BG); - - px_fill_rounded(px, g_win_w, g_win_h, col_idx, row_y + (ITEM_H - 10) / 2, 10, 10, 5, - part_colors[pi % NUM_PART_COLORS]); + c.fill_rounded_rect(col_idx, row_y + (ITEM_H - 10) / 2, 10, 10, 5, + part_colors[pi % NUM_PART_COLORS]); int ry = row_y + (ITEM_H - fh) / 2; - px_text(px, g_win_w, g_win_h, col_name, ry, p.name[0] ? p.name : "(unnamed)", TEXT_COLOR); - px_text(px, g_win_w, g_win_h, col_type, ry, p.typeName, DIM_TEXT); + draw_text_fit(c, col_name, ry, p.name[0] ? p.name : "(unnamed)", + col_type - col_name - 10, theme.text); + draw_text_fit(c, col_type, ry, p.typeName, + col_size - col_type - 10, theme.text_subtle); char sz[24]; format_disk_size(sz, sizeof(sz), p.sectorCount, disk.sectorSizeLog); - px_text(px, g_win_w, g_win_h, col_size, ry, sz, TEXT_COLOR); + c.text(col_size, ry, sz, theme.text); char lba_str[24]; snprintf(lba_str, sizeof(lba_str), "%lu", (unsigned)p.startLba); - px_text(px, g_win_w, g_win_h, col_lba, ry, lba_str, FAINT_TEXT); + c.text(col_lba, ry, lba_str, theme.text_muted); } if (nparts == 0) - px_text(px, g_win_w, g_win_h, col_name, list_y + 8, "No partitions found", FAINT_TEXT); + c.text(col_name, list_y + 8, "No partitions found", theme.text_muted); } -static void render_status(uint32_t* px) { +static void render_status(Canvas& c, const mtk::Theme& theme) { auto& dt = g_state; - int fh = font_h(); + int fh = system_font_height(); int sy = g_win_h - STATUS_H; - px_fill(px, g_win_w, g_win_h, 0, sy, g_win_w, STATUS_H, STATUS_BG_COL); - px_hline(px, g_win_w, g_win_h, 0, sy, g_win_w, BORDER_COLOR); + c.fill_rect(0, sy, g_win_w, STATUS_H, theme.surface_alt); + mtk::draw_separator(c, 0, sy, g_win_w, theme); + if (dt.status[0]) { uint64_t age = montauk::get_milliseconds() - dt.status_time; - Color sc = (age < 5000) - ? Color::from_rgb(0x33, 0x33, 0x33) - : Color::from_rgb(0xAA, 0xAA, 0xAA); - px_text(px, g_win_w, g_win_h, 8, sy + (STATUS_H - fh) / 2, dt.status, sc); + Color color = age < 5000 ? theme.text : theme.text_muted; + c.text(8, sy + (STATUS_H - fh) / 2, dt.status, color); } } -// ============================================================================ -// Render: format dialog window -// ============================================================================ - void render_format_window() { auto& dlg = g_state.fmt_dlg; - if (!dlg.open) return; + if (!dlg.open || !dlg.pixels) return; - uint32_t* px = dlg.pixels; - int dw = FMT_DLG_W, dh = FMT_DLG_H; - int fh = font_h(); + Canvas c(dlg.pixels, FMT_DLG_W, FMT_DLG_H); + mtk::Theme theme = disks_theme(); + c.fill(theme.window_bg); - px_fill(px, dw, dh, 0, 0, dw, dh, BG_COLOR); + int fh = system_font_height(); + draw_centered_text(c, FMT_DLG_W, 12, "Format Partition", theme.text); + draw_centered_text(c, FMT_DLG_W, 12 + fh + 6, dlg.part_desc, theme.text_subtle); - // Title - const char* title = "Format Partition"; - int tw = text_w(title); - px_text(px, dw, dh, (dw - tw) / 2, 12, title, TEXT_COLOR); + int label_y = 12 + fh * 2 + 18; + c.text(16, label_y, "Filesystem", theme.text_muted); - // Partition description - int ddw = text_w(dlg.part_desc); - px_text(px, dw, dh, (dw - ddw) / 2, 12 + fh + 6, dlg.part_desc, DIM_TEXT); - - // Filesystem selector - int sel_y = 12 + fh * 2 + 18; - px_text(px, dw, dh, 16, sel_y, "Filesystem:", TEXT_COLOR); - - int opt_y = sel_y + fh + 8; for (int i = 0; i < NUM_FS_TYPES; i++) { - int ox = 24; - int ow = dw - 48; - int oh = 28; - int iy = opt_y + i * (oh + 4); - - if (i == dlg.selected_fs) { - px_fill_rounded(px, dw, dh, ox, iy, ow, oh, 6, - Color::from_rgb(0xD0, 0xE0, 0xF0)); - px_fill_rounded(px, dw, dh, ox - 1, iy - 1, ow + 2, oh + 2, 7, Color::from_rgb(0x42, 0x7A, 0xB5)); - px_fill_rounded(px, dw, dh, ox, iy, ow, oh, 6, Color::from_rgb(0xD0, 0xE0, 0xF0)); - } else { - px_fill_rounded(px, dw, dh, ox - 1, iy - 1, ow + 2, oh + 2, 7, BORDER_COLOR); - px_fill_rounded(px, dw, dh, ox, iy, ow, oh, 6, BG_COLOR); - } - - // Radio indicator (circular) - int cx = ox + 14; - int cy = iy + oh / 2; - px_fill_rounded(px, dw, dh, cx - 5, cy - 5, 10, 10, 5, DIM_TEXT); - px_fill_rounded(px, dw, dh, cx - 4, cy - 4, 8, 8, 4, BG_COLOR); + Rect option = format_option_rect(FMT_DLG_W, i); if (i == dlg.selected_fs) - px_fill_rounded(px, dw, dh, cx - 3, cy - 3, 6, 6, 3, Color::from_rgb(0x42, 0x7A, 0xB5)); - - px_text(px, dw, dh, cx + 12, iy + (oh - fh) / 2, g_fsTypes[i].name, TEXT_COLOR); + c.fill_rounded_rect(option.x, option.y, option.w, option.h, theme.radius_md, theme.accent_soft); + mtk::draw_radio(c, option, g_fsTypes[i].name, i == dlg.selected_fs, theme); } - // Buttons - int btn_w = 90, btn_h = 30; - int btn_y = dh - btn_h - 16; - int gap = 16; - int total_w = btn_w * 2 + gap; - int bx = (dw - total_w) / 2; - - px_button(px, dw, dh, bx, btn_y, btn_w, btn_h, "Format", true, dlg.hover_format, true); - px_button(px, dw, dh, bx + btn_w + gap, btn_y, btn_w, btn_h, "Cancel", true, dlg.hover_cancel, false); + mtk::draw_modal_actions(c, + dialog_primary_button_rect(FMT_DLG_W, FMT_DLG_H), "Format", + mtk::BUTTON_DANGER, + mtk::widget_state(false, dlg.hover_format), + dialog_secondary_button_rect(FMT_DLG_W, FMT_DLG_H), "Cancel", + mtk::widget_state(false, dlg.hover_cancel), + theme); } -// ============================================================================ -// Render: new partition confirmation dialog -// ============================================================================ - void render_newpart_window() { auto& dlg = g_state.np_dlg; - if (!dlg.open) return; + if (!dlg.open || !dlg.pixels) return; - uint32_t* px = dlg.pixels; - int dw = NP_DLG_W, dh = NP_DLG_H; - int fh = font_h(); + Canvas c(dlg.pixels, NP_DLG_W, NP_DLG_H); + mtk::Theme theme = disks_theme(); + c.fill(theme.window_bg); - px_fill(px, dw, dh, 0, 0, dw, dh, BG_COLOR); - - // Title + int fh = system_font_height(); const char* title = dlg.will_init_gpt ? "Initialize Disk" : "New Partition"; - int tw = text_w(title); - px_text(px, dw, dh, (dw - tw) / 2, 12, title, TEXT_COLOR); + draw_centered_text(c, NP_DLG_W, 12, title, theme.text); + draw_centered_text(c, NP_DLG_W, 12 + fh + 6, dlg.disk_desc, theme.text_subtle); - // Disk description - int ddw = text_w(dlg.disk_desc); - px_text(px, dw, dh, (dw - ddw) / 2, 12 + fh + 6, dlg.disk_desc, DIM_TEXT); - - // Warning lines - Color warn_color = dlg.will_init_gpt - ? Color::from_rgb(0xCC, 0x22, 0x22) - : TEXT_COLOR; - - int wy = 12 + fh * 2 + 20; - int w1w = text_w(dlg.warn_line1); - px_text(px, dw, dh, (dw - w1w) / 2, wy, dlg.warn_line1, warn_color); - int w2w = text_w(dlg.warn_line2); - px_text(px, dw, dh, (dw - w2w) / 2, wy + fh + 4, dlg.warn_line2, warn_color); - - // Buttons - int btn_w = 90, btn_h = 30; - int btn_y = dh - btn_h - 16; - int gap = 16; - int total_w = btn_w * 2 + gap; - int bx = (dw - total_w) / 2; + Color warn_color = dlg.will_init_gpt ? theme.danger : theme.text; + int warn_y = 12 + fh * 2 + 20; + draw_centered_text(c, NP_DLG_W, warn_y, dlg.warn_line1, warn_color); + draw_centered_text(c, NP_DLG_W, warn_y + fh + 4, dlg.warn_line2, warn_color); const char* confirm_label = dlg.will_init_gpt ? "Initialize" : "Create"; - px_button(px, dw, dh, bx, btn_y, btn_w, btn_h, confirm_label, true, dlg.hover_confirm, true); - px_button(px, dw, dh, bx + btn_w + gap, btn_y, btn_w, btn_h, "Cancel", true, dlg.hover_cancel, false); + mtk::ButtonVariant confirm_variant = dlg.will_init_gpt ? mtk::BUTTON_DANGER : mtk::BUTTON_PRIMARY; + mtk::draw_modal_actions(c, + dialog_primary_button_rect(NP_DLG_W, NP_DLG_H), confirm_label, + confirm_variant, + mtk::widget_state(false, dlg.hover_confirm), + dialog_secondary_button_rect(NP_DLG_W, NP_DLG_H), "Cancel", + mtk::widget_state(false, dlg.hover_cancel), + theme); } -// ============================================================================ -// Top-level render -// ============================================================================ - void render(uint32_t* pixels) { - px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR); - render_toolbar(pixels); - render_content(pixels); - render_status(pixels); + if (!pixels) return; + + Canvas c(pixels, g_win_w, g_win_h); + mtk::Theme theme = disks_theme(); + c.fill(theme.window_bg); + render_toolbar(c, theme); + render_content(c, theme); + render_status(c, theme); }