feat: port Disks app to MTK

This commit is contained in:
2026-04-15 18:51:42 +02:00
parent ca954517e9
commit 825777e7a7
3 changed files with 378 additions and 374 deletions
+74 -3
View File
@@ -10,7 +10,7 @@
#include <montauk/string.h>
#include <montauk/heap.h>
#include <gui/gui.hpp>
#include <gui/truetype.hpp>
#include <gui/font.hpp>
extern "C" {
#include <string.h>
@@ -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)
// ============================================================================
+94 -82
View File
@@ -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(); }
+210 -289
View File
@@ -1,184 +1,146 @@
/*
* render.cpp
* MontaukOS Disk Tool rendering
* MontaukOS Disk Tool - rendering
* Copyright (c) 2026 Daniel Hammer
*/
#include "disks.h"
#include <gui/canvas.hpp>
#include <gui/mtk.hpp>
// ============================================================================
// 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;
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);
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++;
}
static void render_content(uint32_t* px) {
if (out <= 0) return;
buf[out] = '.';
buf[out + 1] = '.';
buf[out + 2] = '.';
buf[out + 3] = '\0';
c.text(x, y, buf, color);
}
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,
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);
}