400 lines
14 KiB
C++
400 lines
14 KiB
C++
/*
|
|
* render.cpp
|
|
* MontaukOS Disk Tool — rendering
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "disks.h"
|
|
|
|
// ============================================================================
|
|
// Pixel helpers
|
|
// ============================================================================
|
|
|
|
static void px_fill(uint32_t* px, int bw, int bh,
|
|
int x, int y, int w, int h, Color c) {
|
|
uint32_t v = c.to_pixel();
|
|
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
|
|
int x1 = x + w > bw ? bw : x + w;
|
|
int y1 = y + h > bh ? bh : y + h;
|
|
for (int row = y0; row < y1; row++)
|
|
for (int col = x0; col < x1; col++)
|
|
px[row * bw + col] = v;
|
|
}
|
|
|
|
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c) {
|
|
if (y < 0 || y >= bh) return;
|
|
uint32_t v = c.to_pixel();
|
|
int x0 = x < 0 ? 0 : x;
|
|
int x1 = x + w > bw ? bw : x + w;
|
|
for (int col = x0; col < x1; col++)
|
|
px[y * bw + col] = v;
|
|
}
|
|
|
|
static void px_fill_rounded(uint32_t* px, int bw, int bh,
|
|
int x, int y, int w, int h, int r, Color c) {
|
|
uint32_t v = c.to_pixel();
|
|
for (int row = 0; row < h; row++) {
|
|
int dy = y + row;
|
|
if (dy < 0 || dy >= bh) continue;
|
|
for (int col = 0; col < w; col++) {
|
|
int dx = x + col;
|
|
if (dx < 0 || dx >= bw) continue;
|
|
bool skip = false;
|
|
int cx, cy;
|
|
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
|
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
|
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
|
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
|
if (!skip) px[dy * bw + dx] = v;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void px_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);
|
|
return;
|
|
}
|
|
|
|
// Right-side action buttons
|
|
bool has_sel = dt.selected_part >= 0;
|
|
int rx = g_win_w - 8;
|
|
|
|
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);
|
|
}
|
|
|
|
static void render_content(uint32_t* px) {
|
|
auto& dt = g_state;
|
|
int fh = font_h();
|
|
|
|
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;
|
|
|
|
// 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];
|
|
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;
|
|
|
|
// 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));
|
|
|
|
int part_indices[MAX_PARTS];
|
|
int nparts = get_disk_parts(part_indices, MAX_PARTS);
|
|
uint64_t total_sectors = disk.sectorCount;
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
px_fill_rounded(px, g_win_w, g_win_h, ppx, y + 2, pw, MAP_H - 4, 3, 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);
|
|
}
|
|
}
|
|
|
|
y += MAP_H + 8;
|
|
|
|
// 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;
|
|
|
|
// Partition rows
|
|
int list_bottom = g_win_h - STATUS_H;
|
|
int list_y = y;
|
|
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;
|
|
|
|
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]);
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
if (nparts == 0)
|
|
px_text(px, g_win_w, g_win_h, col_name, list_y + 8, "No partitions found", FAINT_TEXT);
|
|
}
|
|
|
|
static void render_status(uint32_t* px) {
|
|
auto& dt = g_state;
|
|
int fh = font_h();
|
|
|
|
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);
|
|
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);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Render: format dialog window
|
|
// ============================================================================
|
|
|
|
void render_format_window() {
|
|
auto& dlg = g_state.fmt_dlg;
|
|
if (!dlg.open) return;
|
|
|
|
uint32_t* px = dlg.pixels;
|
|
int dw = FMT_DLG_W, dh = FMT_DLG_H;
|
|
int fh = font_h();
|
|
|
|
px_fill(px, dw, dh, 0, 0, dw, dh, BG_COLOR);
|
|
|
|
// Title
|
|
const char* title = "Format Partition";
|
|
int tw = text_w(title);
|
|
px_text(px, dw, dh, (dw - tw) / 2, 12, title, TEXT_COLOR);
|
|
|
|
// 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);
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Render: new partition confirmation dialog
|
|
// ============================================================================
|
|
|
|
void render_newpart_window() {
|
|
auto& dlg = g_state.np_dlg;
|
|
if (!dlg.open) return;
|
|
|
|
uint32_t* px = dlg.pixels;
|
|
int dw = NP_DLG_W, dh = NP_DLG_H;
|
|
int fh = font_h();
|
|
|
|
px_fill(px, dw, dh, 0, 0, dw, dh, BG_COLOR);
|
|
|
|
// Title
|
|
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);
|
|
|
|
// 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;
|
|
|
|
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);
|
|
}
|
|
|
|
// ============================================================================
|
|
// 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);
|
|
}
|