feat: Desktop improvements

This commit is contained in:
2026-02-20 14:13:25 +01:00
parent 4c6b783832
commit fca150e059
25 changed files with 1060 additions and 409 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ LDFLAGS := \
# ---- Source files ----
SRCS := main.cpp font_data.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp
SRCS := main.cpp font_data.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_settings.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
# ---- Target ----
+12 -25
View File
@@ -222,29 +222,21 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) {
CalcState* cs = (CalcState*)win->app_data;
if (!cs) return;
Rect cr = win->content_rect();
int cw = cr.w;
int ch = cr.h;
uint32_t* pixels = win->content;
Canvas c(win);
// Background
uint32_t bg_px = Color::from_rgb(0xF0, 0xF0, 0xF0).to_pixel();
for (int i = 0; i < cw * ch; i++) pixels[i] = bg_px;
c.fill(Color::from_rgb(0xF0, 0xF0, 0xF0));
// Display area
uint32_t disp_px = Color::from_rgb(0x2D, 0x2D, 0x2D).to_pixel();
for (int y = 0; y < CALC_DISPLAY_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = disp_px;
c.fill_rect(0, 0, c.w, CALC_DISPLAY_H, Color::from_rgb(0x2D, 0x2D, 0x2D));
// Display text (right-aligned, 2x scale)
uint32_t disp_text = colors::WHITE.to_pixel();
int text_len = zenith::slen(cs->display_str);
int text_w = text_len * FONT_WIDTH * 2;
int tx = cw - text_w - 12;
int tx = c.w - text_w - 12;
int ty = (CALC_DISPLAY_H - FONT_HEIGHT * 2) / 2;
if (tx < 4) tx = 4;
draw_text_to_pixels_2x(pixels, cw, ch, tx, ty, cs->display_str, disp_text);
c.text_2x(tx, ty, cs->display_str, colors::WHITE);
// Button grid
int grid_y = CALC_DISPLAY_H + CALC_BTN_PAD;
@@ -264,30 +256,25 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) {
}
// Button color
uint32_t btn_px;
Color btn_color;
if (col == 3) {
// Operator column: accent blue
btn_px = colors::ACCENT.to_pixel();
btn_color = colors::ACCENT;
} else if (row == 0) {
// Function row: gray
btn_px = Color::from_rgb(0xD0, 0xD0, 0xD0).to_pixel();
btn_color = Color::from_rgb(0xD0, 0xD0, 0xD0);
} else {
// Digit buttons: light gray
btn_px = Color::from_rgb(0xE8, 0xE8, 0xE8).to_pixel();
btn_color = Color::from_rgb(0xE8, 0xE8, 0xE8);
}
// Draw button
for (int dy = 0; dy < CALC_BTN_H && by + dy < ch; dy++)
for (int dx = 0; dx < bw && bx + dx < cw; dx++)
pixels[(by + dy) * cw + (bx + dx)] = btn_px;
c.fill_rect(bx, by, bw, CALC_BTN_H, btn_color);
// Button text
const char* label = calc_labels[row][col];
uint32_t label_px = (col == 3) ? colors::WHITE.to_pixel() : colors::TEXT_COLOR.to_pixel();
Color label_color = (col == 3) ? colors::WHITE : colors::TEXT_COLOR;
int label_w = zenith::slen(label) * FONT_WIDTH;
int lx = bx + (bw - label_w) / 2;
int ly = by + (CALC_BTN_H - FONT_HEIGHT) / 2;
draw_text_to_pixels(pixels, cw, ch, lx, ly, label, label_px);
c.text(lx, ly, label, label_color);
}
}
}
+74 -121
View File
@@ -248,24 +248,16 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
FileManagerState* fm = (FileManagerState*)win->app_data;
if (!fm) return;
Rect cr = win->content_rect();
int cw = cr.w;
int ch = cr.h;
uint32_t* pixels = win->content;
uint32_t bg_px = colors::WINDOW_BG.to_pixel();
for (int i = 0; i < cw * ch; i++) pixels[i] = bg_px;
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
uint32_t sep_px = colors::BORDER.to_pixel();
uint32_t toolbar_px = Color::from_rgb(0xF5, 0xF5, 0xF5).to_pixel();
Canvas c(win);
c.fill(colors::WINDOW_BG);
Color toolbar_color = Color::from_rgb(0xF5, 0xF5, 0xF5);
Color btn_bg = Color::from_rgb(0xE8, 0xE8, 0xE8);
Color dim = Color::from_rgb(0x88, 0x88, 0x88);
DesktopState* ds = fm->desktop;
// ---- Toolbar (32px) ----
for (int y = 0; y < FM_TOOLBAR_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = toolbar_px;
c.fill_rect(0, 0, c.w, FM_TOOLBAR_H, toolbar_color);
// Toolbar buttons: Back, Forward, Up, Home
struct ToolBtn { int x; SvgIcon* icon; };
@@ -279,79 +271,53 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
for (int i = 0; i < 4; i++) {
int bx = btns[i].x;
int by = 4;
// Button background
uint32_t btn_px = Color::from_rgb(0xE8, 0xE8, 0xE8).to_pixel();
for (int dy = 0; dy < 24 && by + dy < ch; dy++)
for (int dx = 0; dx < 24 && bx + dx < cw; dx++)
pixels[(by + dy) * cw + (bx + dx)] = btn_px;
c.fill_rect(bx, by, 24, 24, btn_bg);
if (btns[i].icon) {
int ix = bx + (24 - btns[i].icon->width) / 2;
int iy = by + (24 - btns[i].icon->height) / 2;
blit_icon_to_pixels(pixels, cw, ch, ix, iy, *btns[i].icon);
c.icon(ix, iy, *btns[i].icon);
}
}
// Toggle view button (5th toolbar button)
{
int bx = 120, by = 4;
uint32_t btn_px = Color::from_rgb(0xE8, 0xE8, 0xE8).to_pixel();
for (int dy = 0; dy < 24 && by + dy < ch; dy++)
for (int dx = 0; dx < 24 && bx + dx < cw; dx++)
pixels[(by + dy) * cw + (bx + dx)] = btn_px;
uint32_t glyph_px = colors::TEXT_COLOR.to_pixel();
c.fill_rect(bx, by, 24, 24, btn_bg);
if (fm->grid_view) {
// Draw 4 small squares to indicate grid mode
for (int r = 0; r < 2; r++)
for (int c = 0; c < 2; c++)
for (int dy = 0; dy < 6; dy++)
for (int dx = 0; dx < 6; dx++)
if (by + 5 + r * 8 + dy < ch && bx + 5 + c * 8 + dx < cw)
pixels[(by + 5 + r * 8 + dy) * cw + (bx + 5 + c * 8 + dx)] = glyph_px;
for (int cc = 0; cc < 2; cc++)
c.fill_rect(bx + 5 + cc * 8, by + 5 + r * 8, 6, 6, colors::TEXT_COLOR);
} else {
// Draw 3 horizontal lines to indicate list mode
for (int r = 0; r < 3; r++)
for (int dx = 0; dx < 14; dx++)
for (int dy = 0; dy < 2; dy++)
if (by + 5 + r * 5 + dy < ch && bx + 5 + dx < cw)
pixels[(by + 5 + r * 5 + dy) * cw + (bx + 5 + dx)] = glyph_px;
c.fill_rect(bx + 5, by + 5 + r * 5, 14, 2, colors::TEXT_COLOR);
}
}
// Toolbar separator
int sep_y = FM_TOOLBAR_H - 1;
if (sep_y < ch) {
for (int x = 0; x < cw; x++)
pixels[sep_y * cw + x] = sep_px;
}
c.hline(0, FM_TOOLBAR_H - 1, c.w, colors::BORDER);
// ---- Path bar ----
int pathbar_y = FM_TOOLBAR_H;
uint32_t pathbar_px = Color::from_rgb(0xF0, 0xF0, 0xF0).to_pixel();
for (int y = pathbar_y; y < pathbar_y + FM_PATHBAR_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = pathbar_px;
draw_text_to_pixels(pixels, cw, ch, 8, pathbar_y + 4, fm->current_path, text_px);
c.fill_rect(0, pathbar_y, c.w, FM_PATHBAR_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
c.text(8, pathbar_y + 4, fm->current_path, colors::TEXT_COLOR);
// Path bar separator
sep_y = pathbar_y + FM_PATHBAR_H - 1;
if (sep_y < ch) {
for (int x = 0; x < cw; x++)
pixels[sep_y * cw + x] = sep_px;
}
c.hline(0, pathbar_y + FM_PATHBAR_H - 1, c.w, colors::BORDER);
if (fm->grid_view) {
// ---- Grid View ----
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H;
int list_h = ch - list_y;
int cols = (cw - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
int list_h = c.h - list_y;
int cols = (c.w - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1;
int rows = (fm->entry_count + cols - 1) / cols;
int content_h = rows * FM_GRID_CELL_H;
// Update scrollbar
fm->scrollbar.bounds = {cw - FM_SCROLLBAR_W, list_y, FM_SCROLLBAR_W, list_h};
fm->scrollbar.bounds = {c.w - FM_SCROLLBAR_W, list_y, FM_SCROLLBAR_W, list_h};
fm->scrollbar.content_height = content_h;
fm->scrollbar.view_height = list_h;
@@ -362,32 +328,34 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
int cell_y = list_y + row * FM_GRID_CELL_H - fm->scrollbar.scroll_offset;
// Skip if entirely off-screen
if (cell_y + FM_GRID_CELL_H <= list_y || cell_y >= ch) continue;
if (cell_y + FM_GRID_CELL_H <= list_y || cell_y >= c.h) continue;
// Selection highlight
if (i == fm->selected) {
uint32_t sel_px = colors::MENU_HOVER.to_pixel();
for (int y = gui_max(cell_y, list_y); y < gui_min(cell_y + FM_GRID_CELL_H, ch); y++)
for (int x = cell_x; x < gui_min(cell_x + FM_GRID_CELL_W, cw - FM_SCROLLBAR_W); x++)
pixels[y * cw + x] = sel_px;
int sy = gui_max(cell_y, list_y);
int sh = gui_min(cell_y + FM_GRID_CELL_H, c.h) - sy;
int sw = gui_min(FM_GRID_CELL_W, c.w - FM_SCROLLBAR_W - cell_x);
if (sh > 0 && sw > 0)
c.fill_rect(cell_x, sy, sw, sh, colors::MENU_HOVER);
}
// Large icon centered horizontally
int icon_x = cell_x + (FM_GRID_CELL_W - FM_GRID_ICON) / 2;
int icon_y = cell_y + FM_GRID_PAD;
if (ds && fm->entry_types[i] == 1 && ds->icon_folder_lg.pixels) {
blit_icon_to_pixels(pixels, cw, ch, icon_x, icon_y, ds->icon_folder_lg);
c.icon(icon_x, icon_y, ds->icon_folder_lg);
} else if (ds && fm->entry_types[i] == 2 && ds->icon_exec_lg.pixels) {
blit_icon_to_pixels(pixels, cw, ch, icon_x, icon_y, ds->icon_exec_lg);
c.icon(icon_x, icon_y, ds->icon_exec_lg);
} else if (ds && ds->icon_file_lg.pixels) {
blit_icon_to_pixels(pixels, cw, ch, icon_x, icon_y, ds->icon_file_lg);
c.icon(icon_x, icon_y, ds->icon_file_lg);
} else {
uint32_t icon_px = fm->is_dir[i]
? Color::from_rgb(0xFF, 0xBD, 0x2E).to_pixel()
: Color::from_rgb(0x90, 0x90, 0x90).to_pixel();
for (int dy = 0; dy < FM_GRID_ICON && icon_y + dy < ch && icon_y + dy >= list_y; dy++)
for (int dx = 0; dx < FM_GRID_ICON && icon_x + dx < cw; dx++)
pixels[(icon_y + dy) * cw + (icon_x + dx)] = icon_px;
Color icon_c = fm->is_dir[i]
? Color::from_rgb(0xFF, 0xBD, 0x2E)
: Color::from_rgb(0x90, 0x90, 0x90);
int iy_clip = gui_max(icon_y, list_y);
int ih_clip = FM_GRID_ICON - (iy_clip - icon_y);
if (ih_clip > 0)
c.fill_rect(icon_x, iy_clip, FM_GRID_ICON, ih_clip, icon_c);
}
// Filename centered below icon, truncated if needed
@@ -405,52 +373,42 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
int tx = cell_x + (FM_GRID_CELL_W - tw) / 2;
if (tx < cell_x) tx = cell_x;
int ty = icon_y + FM_GRID_ICON + 2;
if (ty >= list_y && ty + FONT_HEIGHT <= ch)
draw_text_to_pixels(pixels, cw, ch, tx, ty, label, text_px);
if (ty >= list_y && ty + FONT_HEIGHT <= c.h)
c.text(tx, ty, label, colors::TEXT_COLOR);
}
} else {
// ---- List View ----
// ---- Column headers ----
int header_y = FM_TOOLBAR_H + FM_PATHBAR_H;
uint32_t header_px = Color::from_rgb(0xF8, 0xF8, 0xF8).to_pixel();
for (int y = header_y; y < header_y + FM_HEADER_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = header_px;
c.fill_rect(0, header_y, c.w, FM_HEADER_H, Color::from_rgb(0xF8, 0xF8, 0xF8));
uint32_t dim_px = Color::from_rgb(0x88, 0x88, 0x88).to_pixel();
int name_col_x = 8;
int size_col_x = cw - FM_SCROLLBAR_W - 120;
int type_col_x = cw - FM_SCROLLBAR_W - 60;
int size_col_x = c.w - FM_SCROLLBAR_W - 120;
int type_col_x = c.w - FM_SCROLLBAR_W - 60;
draw_text_to_pixels(pixels, cw, ch, name_col_x, header_y + 2, "Name", dim_px);
c.text(name_col_x, header_y + 2, "Name", dim);
if (size_col_x > 100)
draw_text_to_pixels(pixels, cw, ch, size_col_x, header_y + 2, "Size", dim_px);
c.text(size_col_x, header_y + 2, "Size", dim);
if (type_col_x > 160)
draw_text_to_pixels(pixels, cw, ch, type_col_x, header_y + 2, "Type", dim_px);
c.text(type_col_x, header_y + 2, "Type", dim);
// Header separator
sep_y = header_y + FM_HEADER_H - 1;
if (sep_y < ch) {
for (int x = 0; x < cw; x++)
pixels[sep_y * cw + x] = sep_px;
}
c.hline(0, header_y + FM_HEADER_H - 1, c.w, colors::BORDER);
// Column separator lines
if (size_col_x > 100) {
for (int y = header_y; y < ch; y++)
if (size_col_x - 4 >= 0 && size_col_x - 4 < cw)
pixels[y * cw + (size_col_x - 4)] = sep_px;
c.vline(size_col_x - 4, header_y, c.h - header_y, colors::BORDER);
}
// ---- File entries ----
int list_y = header_y + FM_HEADER_H;
int list_h = ch - list_y;
int list_h = c.h - list_y;
int visible_items = list_h / FM_ITEM_H;
int content_h = fm->entry_count * FM_ITEM_H;
// Update scrollbar
fm->scrollbar.bounds = {cw - FM_SCROLLBAR_W, list_y, FM_SCROLLBAR_W, list_h};
fm->scrollbar.bounds = {c.w - FM_SCROLLBAR_W, list_y, FM_SCROLLBAR_W, list_h};
fm->scrollbar.content_height = content_h;
fm->scrollbar.view_height = list_h;
@@ -458,78 +416,73 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
for (int i = scroll_items; i < fm->entry_count && (i - scroll_items) < visible_items + 1; i++) {
int iy = list_y + (i - scroll_items) * FM_ITEM_H - (fm->scrollbar.scroll_offset % FM_ITEM_H);
if (iy + FM_ITEM_H <= list_y || iy >= ch) continue;
if (iy + FM_ITEM_H <= list_y || iy >= c.h) continue;
// Highlight selected
if (i == fm->selected) {
uint32_t sel_px = colors::MENU_HOVER.to_pixel();
for (int y = gui_max(iy, list_y); y < gui_min(iy + FM_ITEM_H, ch); y++)
for (int x = 0; x < cw - FM_SCROLLBAR_W; x++)
pixels[y * cw + x] = sel_px;
int sy = gui_max(iy, list_y);
int sh = gui_min(iy + FM_ITEM_H, c.h) - sy;
if (sh > 0)
c.fill_rect(0, sy, c.w - FM_SCROLLBAR_W, sh, colors::MENU_HOVER);
}
// Icon
int icon_x = 8;
int icon_y = iy + (FM_ITEM_H - 16) / 2;
int ico_x = 8;
int ico_y = iy + (FM_ITEM_H - 16) / 2;
if (ds && fm->entry_types[i] == 1 && ds->icon_folder.pixels) {
blit_icon_to_pixels(pixels, cw, ch, icon_x, icon_y, ds->icon_folder);
c.icon(ico_x, ico_y, ds->icon_folder);
} else if (ds && fm->entry_types[i] == 2 && ds->icon_exec.pixels) {
blit_icon_to_pixels(pixels, cw, ch, icon_x, icon_y, ds->icon_exec);
c.icon(ico_x, ico_y, ds->icon_exec);
} else if (ds && ds->icon_file.pixels) {
blit_icon_to_pixels(pixels, cw, ch, icon_x, icon_y, ds->icon_file);
c.icon(ico_x, ico_y, ds->icon_file);
} else {
uint32_t icon_px = fm->is_dir[i]
? Color::from_rgb(0xFF, 0xBD, 0x2E).to_pixel()
: Color::from_rgb(0x90, 0x90, 0x90).to_pixel();
for (int dy = 0; dy < 16 && icon_y + dy < ch && icon_y + dy >= list_y; dy++)
for (int dx = 0; dx < 16 && icon_x + dx < cw; dx++)
pixels[(icon_y + dy) * cw + (icon_x + dx)] = icon_px;
Color icon_c = fm->is_dir[i]
? Color::from_rgb(0xFF, 0xBD, 0x2E)
: Color::from_rgb(0x90, 0x90, 0x90);
int iy_clip = gui_max(ico_y, list_y);
int ih_clip = 16 - (iy_clip - ico_y);
if (ih_clip > 0)
c.fill_rect(ico_x, iy_clip, 16, ih_clip, icon_c);
}
// Name
int tx = 30;
int ty = iy + (FM_ITEM_H - FONT_HEIGHT) / 2;
if (ty >= list_y && ty + FONT_HEIGHT <= ch)
draw_text_to_pixels(pixels, cw, ch, tx, ty, fm->entry_names[i], text_px);
if (ty >= list_y && ty + FONT_HEIGHT <= c.h)
c.text(tx, ty, fm->entry_names[i], colors::TEXT_COLOR);
// Size
if (size_col_x > 100 && !fm->is_dir[i] && ty >= list_y && ty + FONT_HEIGHT <= ch) {
if (size_col_x > 100 && !fm->is_dir[i] && ty >= list_y && ty + FONT_HEIGHT <= c.h) {
char size_str[16];
format_size(size_str, fm->entry_sizes[i]);
draw_text_to_pixels(pixels, cw, ch, size_col_x, ty, size_str, dim_px);
c.text(size_col_x, ty, size_str, dim);
}
// Type
if (type_col_x > 160 && ty >= list_y && ty + FONT_HEIGHT <= ch) {
if (type_col_x > 160 && ty >= list_y && ty + FONT_HEIGHT <= c.h) {
const char* type_str = "File";
if (fm->entry_types[i] == 1) type_str = "Dir";
else if (fm->entry_types[i] == 2) type_str = "Exec";
draw_text_to_pixels(pixels, cw, ch, type_col_x, ty, type_str, dim_px);
c.text(type_col_x, ty, type_str, dim);
}
}
}
// ---- Scrollbar ----
// Draw scrollbar directly to pixels
if (fm->scrollbar.content_height > fm->scrollbar.view_height) {
uint32_t sb_bg = colors::SCROLLBAR_BG.to_pixel();
uint32_t sb_fg = (fm->scrollbar.hovered || fm->scrollbar.dragging)
? fm->scrollbar.hover_fg.to_pixel() : fm->scrollbar.fg.to_pixel();
Color sb_fg_color = (fm->scrollbar.hovered || fm->scrollbar.dragging)
? fm->scrollbar.hover_fg : fm->scrollbar.fg;
int sbx = fm->scrollbar.bounds.x;
int sby = fm->scrollbar.bounds.y;
int sbw = fm->scrollbar.bounds.w;
int sbh = fm->scrollbar.bounds.h;
for (int y = sby; y < sby + sbh && y < ch; y++)
for (int x = sbx; x < sbx + sbw && x < cw; x++)
pixels[y * cw + x] = sb_bg;
c.fill_rect(sbx, sby, sbw, sbh, colors::SCROLLBAR_BG);
int th = fm->scrollbar.thumb_height();
int tty = fm->scrollbar.thumb_y();
for (int y = tty; y < tty + th && y < sby + sbh && y < ch; y++)
for (int x = sbx + 1; x < sbx + sbw - 1 && x < cw; x++)
pixels[y * cw + x] = sb_fg;
c.fill_rect(sbx + 1, tty, sbw - 2, th, sb_fg_color);
}
}
+88
View File
@@ -0,0 +1,88 @@
/*
* app_settings.cpp
* ZenithOS Desktop - About application
* Copyright (c) 2026 Daniel Hammer
*/
#include "apps_common.hpp"
// ============================================================================
// About state and callbacks
// ============================================================================
struct AboutState {
Zenith::SysInfo sys_info;
uint64_t uptime_ms;
};
static void about_on_draw(Window* win, Framebuffer& fb) {
AboutState* st = (AboutState*)win->app_data;
if (!st) return;
st->uptime_ms = zenith::get_milliseconds();
Canvas c(win);
c.fill(colors::WINDOW_BG);
Color dim = Color::from_rgb(0x88, 0x88, 0x88);
int x = 16;
int y = 20;
char line[128];
int line_h = FONT_HEIGHT + 6;
// OS name in 2x size
c.text_2x(x, y, st->sys_info.osName, colors::ACCENT);
y += FONT_HEIGHT * 2 + 8;
snprintf(line, sizeof(line), "Version %s", st->sys_info.osVersion);
c.text(x, y, line, colors::TEXT_COLOR);
y += line_h + 8;
c.hline(x, y, c.w - 2 * x, colors::BORDER);
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);
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);
snprintf(line, sizeof(line), "Build: %s %s", __DATE__, __TIME__);
c.text(x, y, line, colors::TEXT_COLOR);
y += line_h + 16;
c.hline(x, y, c.w - 2 * x, colors::BORDER);
y += 12;
c.kv_line(x, &y, "A hobby operating system built from scratch.", dim, line_h);
c.text(x, y, "Copyright (c) 2026 Daniel Hammer", dim);
}
static void about_on_close(Window* win) {
if (win->app_data) {
zenith::mfree(win->app_data);
win->app_data = nullptr;
}
}
// ============================================================================
// About launcher
// ============================================================================
void open_settings(DesktopState* ds) {
int idx = desktop_create_window(ds, "About", 280, 150, 380, 280);
if (idx < 0) return;
Window* win = &ds->windows[idx];
AboutState* st = (AboutState*)zenith::malloc(sizeof(AboutState));
zenith::memset(st, 0, sizeof(AboutState));
zenith::get_info(&st->sys_info);
st->uptime_ms = zenith::get_milliseconds();
win->app_data = st;
win->on_draw = about_on_draw;
win->on_close = about_on_close;
}
+16 -35
View File
@@ -20,52 +20,38 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
SysInfoState* si = (SysInfoState*)win->app_data;
if (!si) return;
// Refresh uptime
si->uptime_ms = zenith::get_milliseconds();
Rect cr = win->content_rect();
int cw = cr.w;
int ch = cr.h;
uint32_t* pixels = win->content;
Canvas c(win);
c.fill(colors::WINDOW_BG);
// Fill background
uint32_t bg_px = colors::WINDOW_BG.to_pixel();
for (int i = 0; i < cw * ch; i++) pixels[i] = bg_px;
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
uint32_t accent_px = colors::ACCENT.to_pixel();
int y = 16;
int x = 16;
char line[128];
// Title
draw_text_to_pixels(pixels, cw, ch, x, y, "System Information", accent_px);
c.text(x, y, "System Information", colors::ACCENT);
y += FONT_HEIGHT + 12;
// Separator
uint32_t sep_px = colors::BORDER.to_pixel();
for (int sx = x; sx < cw - x && y < ch; sx++)
pixels[y * cw + sx] = sep_px;
c.hline(x, y, c.w - 2 * x, colors::BORDER);
y += 8;
// OS Name
snprintf(line, sizeof(line), "OS: %s", si->sys_info.osName);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// OS Version
snprintf(line, sizeof(line), "Version: %s", si->sys_info.osVersion);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// API Version
snprintf(line, sizeof(line), "API: %d", (int)si->sys_info.apiVersion);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// Max Processes
snprintf(line, sizeof(line), "Max PIDs: %d", (int)si->sys_info.maxProcesses);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
c.text(x, y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 12;
// Uptime
@@ -73,15 +59,14 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
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);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
c.text(x, y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 12;
// Network section
draw_text_to_pixels(pixels, cw, ch, x, y, "Network", accent_px);
c.text(x, y, "Network", colors::ACCENT);
y += FONT_HEIGHT + 8;
for (int sx = x; sx < cw - x && y < ch; sx++)
pixels[y * cw + sx] = sep_px;
c.hline(x, y, c.w - 2 * x, colors::BORDER);
y += 8;
// IP Address
@@ -89,39 +74,35 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
snprintf(line, sizeof(line), "IP: %d.%d.%d.%d",
(int)(ip & 0xFF), (int)((ip >> 8) & 0xFF),
(int)((ip >> 16) & 0xFF), (int)((ip >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// Subnet
uint32_t mask = si->net_cfg.subnetMask;
snprintf(line, sizeof(line), "Subnet: %d.%d.%d.%d",
(int)(mask & 0xFF), (int)((mask >> 8) & 0xFF),
(int)((mask >> 16) & 0xFF), (int)((mask >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// Gateway
uint32_t gw = si->net_cfg.gateway;
snprintf(line, sizeof(line), "Gateway: %d.%d.%d.%d",
(int)(gw & 0xFF), (int)((gw >> 8) & 0xFF),
(int)((gw >> 16) & 0xFF), (int)((gw >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// DNS
uint32_t dns = si->net_cfg.dnsServer;
snprintf(line, sizeof(line), "DNS: %d.%d.%d.%d",
(int)(dns & 0xFF), (int)((dns >> 8) & 0xFF),
(int)((dns >> 16) & 0xFF), (int)((dns >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
y += FONT_HEIGHT + 6;
c.kv_line(x, &y, line, colors::TEXT_COLOR);
// MAC Address
snprintf(line, sizeof(line), "MAC: %02x:%02x:%02x:%02x:%02x:%02x",
(unsigned)si->net_cfg.macAddress[0], (unsigned)si->net_cfg.macAddress[1],
(unsigned)si->net_cfg.macAddress[2], (unsigned)si->net_cfg.macAddress[3],
(unsigned)si->net_cfg.macAddress[4], (unsigned)si->net_cfg.macAddress[5]);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px);
c.text(x, y, line, colors::TEXT_COLOR);
}
static void sysinfo_on_close(Window* win) {
+26 -50
View File
@@ -287,38 +287,24 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
TextEditorState* te = (TextEditorState*)win->app_data;
if (!te) return;
Rect cr = win->content_rect();
int cw = cr.w;
int ch = cr.h;
uint32_t* pixels = win->content;
Canvas c(win);
c.fill(colors::WINDOW_BG);
// Background
uint32_t bg_px = colors::WINDOW_BG.to_pixel();
for (int i = 0; i < cw * ch; i++) pixels[i] = bg_px;
int text_area_h = ch - TE_STATUS_H;
int text_area_h = c.h - TE_STATUS_H;
int visible_lines = text_area_h / FONT_HEIGHT;
te_ensure_cursor_visible(te, visible_lines);
// Line number gutter background
uint32_t gutter_px = Color::from_rgb(0xF0, 0xF0, 0xF0).to_pixel();
for (int y = 0; y < text_area_h && y < ch; y++)
for (int x = 0; x < TE_LINE_NUM_W && x < cw; x++)
pixels[y * cw + x] = gutter_px;
c.fill_rect(0, 0, TE_LINE_NUM_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0));
// Gutter separator
uint32_t sep_px = colors::BORDER.to_pixel();
if (TE_LINE_NUM_W < cw) {
for (int y = 0; y < text_area_h && y < ch; y++)
pixels[y * cw + TE_LINE_NUM_W] = sep_px;
}
c.vline(TE_LINE_NUM_W, 0, text_area_h, colors::BORDER);
// Draw lines
Color linenum_color = Color::from_rgb(0x99, 0x99, 0x99);
Color cursor_line_color = Color::from_rgb(0xFF, 0xFD, 0xE8);
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
uint32_t linenum_px = Color::from_rgb(0x99, 0x99, 0x99).to_pixel();
uint32_t cursor_line_px = Color::from_rgb(0xFF, 0xFD, 0xE8).to_pixel();
uint32_t cursor_px = colors::ACCENT.to_pixel();
int text_start_x = TE_LINE_NUM_W + 4;
@@ -331,36 +317,36 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
// Cursor line highlighting
if (line == te->cursor_line) {
for (int y = py; y < py + FONT_HEIGHT && y < text_area_h; y++)
for (int x = TE_LINE_NUM_W + 1; x < cw; x++)
pixels[y * cw + x] = cursor_line_px;
int hl_h = gui_min(FONT_HEIGHT, text_area_h - py);
if (hl_h > 0)
c.fill_rect(TE_LINE_NUM_W + 1, py, c.w - TE_LINE_NUM_W - 1, hl_h, cursor_line_color);
}
// Line number
char num_str[8];
snprintf(num_str, 8, "%4d", line + 1);
draw_text_to_pixels(pixels, cw, ch, 4, py, num_str, linenum_px);
c.text(4, py, num_str, linenum_color);
// Line text
// Line text (per-character rendering with horizontal scroll clipping)
int line_start = te->line_offsets[line];
int line_len = te_line_length(te, line);
for (int ci = 0; ci < line_len; ci++) {
int px = text_start_x + ci * FONT_WIDTH - te->scroll_x;
if (px + FONT_WIDTH <= TE_LINE_NUM_W + 1) continue;
if (px >= cw) break;
if (px >= c.w) break;
char c = te->buffer[line_start + ci];
if (c >= 32 || c < 0) {
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT];
char ch = te->buffer[line_start + ci];
if (ch >= 32 || ch < 0) {
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
for (int fy = 0; fy < FONT_HEIGHT && py + fy < text_area_h; fy++) {
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = px + fx;
int dy = py + fy;
if (dx > TE_LINE_NUM_W && dx < cw && dy >= 0 && dy < text_area_h)
pixels[dy * cw + dx] = text_px;
if (dx > TE_LINE_NUM_W && dx < c.w && dy >= 0 && dy < text_area_h)
c.pixels[dy * c.w + dx] = text_px;
}
}
}
@@ -370,24 +356,17 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
// Draw cursor
if (line == te->cursor_line) {
int cx = text_start_x + te->cursor_col * FONT_WIDTH - te->scroll_x;
if (cx > TE_LINE_NUM_W && cx + 2 <= cw) {
for (int y = py; y < py + FONT_HEIGHT && y < text_area_h; y++) {
pixels[y * cw + cx] = cursor_px;
if (cx + 1 < cw)
pixels[y * cw + cx + 1] = cursor_px;
}
if (cx > TE_LINE_NUM_W && cx + 2 <= c.w) {
int cur_h = gui_min(FONT_HEIGHT, text_area_h - py);
if (cur_h > 0)
c.fill_rect(cx, py, 2, cur_h, colors::ACCENT);
}
}
}
// ---- Status bar ----
int status_y = ch - TE_STATUS_H;
uint32_t status_bg = Color::from_rgb(0x2B, 0x3E, 0x50).to_pixel();
uint32_t status_fg = colors::PANEL_TEXT.to_pixel();
for (int y = status_y; y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = status_bg;
int status_y = c.h - TE_STATUS_H;
c.fill_rect(0, status_y, c.w, TE_STATUS_H, Color::from_rgb(0x2B, 0x3E, 0x50));
// Filename + modified flag
char status_left[128];
@@ -396,16 +375,13 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
} else {
snprintf(status_left, 128, " Untitled%s", te->modified ? " [modified]" : "");
}
draw_text_to_pixels(pixels, cw, ch, 4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2,
status_left, status_fg);
c.text(4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_left, colors::PANEL_TEXT);
// Cursor position (right side)
char status_right[32];
snprintf(status_right, 32, "Ln %d, Col %d ", te->cursor_line + 1, te->cursor_col + 1);
int sr_w = zenith::slen(status_right) * FONT_WIDTH;
draw_text_to_pixels(pixels, cw, ch, cw - sr_w - 4,
status_y + (TE_STATUS_H - FONT_HEIGHT) / 2,
status_right, status_fg);
c.text(c.w - sr_w - 4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_right, colors::PANEL_TEXT);
}
// ============================================================================
+33 -79
View File
@@ -22,7 +22,7 @@ static constexpr int WIKI_SCROLLBAR_W = 12;
struct WikiDisplayLine {
char text[256];
uint32_t color;
Color color;
};
struct WikiState {
@@ -121,7 +121,7 @@ static int wiki_extract_json_string(const char* buf, int len, const char* key,
// Display line building (word-wrap adapted for pixel widths)
// ============================================================================
static void wiki_add_line(WikiState* ws, const char* text, int len, uint32_t color) {
static void wiki_add_line(WikiState* ws, const char* text, int len, Color color) {
if (ws->lineCount >= ws->lineCap) return;
WikiDisplayLine* dl = &ws->lines[ws->lineCount];
int copyLen = len;
@@ -133,7 +133,7 @@ static void wiki_add_line(WikiState* ws, const char* text, int len, uint32_t col
}
static void wiki_wrap_text(WikiState* ws, const char* text, int textLen,
int maxChars, uint32_t color) {
int maxChars, Color color) {
if (textLen <= 0 || maxChars <= 0) return;
const char* p = text;
const char* end = text + textLen;
@@ -171,18 +171,18 @@ static void wiki_build_display(WikiState* ws, const char* title,
int maxChars = (contentW - 24 - WIKI_SCROLLBAR_W) / FONT_WIDTH;
if (maxChars < 20) maxChars = 20;
uint32_t accent_px = colors::ACCENT.to_pixel();
uint32_t green_px = Color::from_rgb(0x2E, 0x7D, 0x32).to_pixel();
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
Color accent_c = colors::ACCENT;
Color green_c = Color::from_rgb(0x2E, 0x7D, 0x32);
Color text_c = colors::TEXT_COLOR;
// Title
if (title && title[0]) {
wiki_wrap_text(ws, title, slen(title), maxChars, accent_px);
wiki_wrap_text(ws, title, slen(title), maxChars, accent_c);
}
// Blank separator
if (ws->lineCount > 0)
wiki_add_line(ws, "", 0, text_px);
wiki_add_line(ws, "", 0, text_c);
// Process extract line by line
const char* p = extract;
@@ -195,7 +195,7 @@ static void wiki_build_display(WikiState* ws, const char* title,
if (p < end) p++;
if (lineLen == 0) {
wiki_add_line(ws, "", 0, text_px);
wiki_add_line(ws, "", 0, text_c);
continue;
}
@@ -208,20 +208,20 @@ static void wiki_build_display(WikiState* ws, const char* title,
while (ei > si && lineStart[ei - 1] == '=') ei--;
while (ei > si && lineStart[ei - 1] == ' ') ei--;
wiki_add_line(ws, "", 0, text_px);
wiki_add_line(ws, "", 0, text_c);
if (ei > si) {
char secBuf[256];
int secLen = ei - si;
if (secLen > 255) secLen = 255;
for (int i = 0; i < secLen; i++) secBuf[i] = lineStart[si + i];
secBuf[secLen] = '\0';
wiki_add_line(ws, secBuf, secLen, green_px);
wiki_add_line(ws, secBuf, secLen, green_c);
}
continue;
}
// Regular text
wiki_wrap_text(ws, lineStart, lineLen, maxChars, text_px);
wiki_wrap_text(ws, lineStart, lineLen, maxChars, text_c);
}
}
@@ -280,111 +280,68 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) {
WikiState* ws = (WikiState*)win->app_data;
if (!ws) return;
Rect cr = win->content_rect();
int cw = cr.w;
int ch = cr.h;
uint32_t* pixels = win->content;
// Fill background
uint32_t bg_px = colors::WINDOW_BG.to_pixel();
for (int i = 0; i < cw * ch; i++) pixels[i] = bg_px;
Canvas c(win);
c.fill(colors::WINDOW_BG);
// ---- Toolbar background ----
uint32_t toolbar_bg = Color::from_rgb(0xF5, 0xF5, 0xF5).to_pixel();
for (int y = 0; y < WIKI_TOOLBAR_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = toolbar_bg;
c.fill_rect(0, 0, c.w, WIKI_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
// Toolbar separator line
uint32_t sep_px = colors::BORDER.to_pixel();
if (WIKI_TOOLBAR_H < ch)
for (int x = 0; x < cw; x++)
pixels[WIKI_TOOLBAR_H * cw + x] = sep_px;
c.hline(0, WIKI_TOOLBAR_H, c.w, colors::BORDER);
// ---- Draw search box outline ----
int tb_x = 8;
int tb_y = 6;
int tb_w = cw - 90;
int tb_w = c.w - 90;
int tb_h = 24;
if (tb_w < 100) tb_w = 100;
// TextBox background
uint32_t white_px = colors::WHITE.to_pixel();
uint32_t border_px = colors::BORDER.to_pixel();
for (int y = tb_y; y < tb_y + tb_h && y < ch; y++)
for (int x = tb_x; x < tb_x + tb_w && x < cw; x++)
pixels[y * cw + x] = white_px;
// Border
for (int x = tb_x; x < tb_x + tb_w && x < cw; x++) {
if (tb_y < ch) pixels[tb_y * cw + x] = border_px;
if (tb_y + tb_h - 1 < ch) pixels[(tb_y + tb_h - 1) * cw + x] = border_px;
}
for (int y = tb_y; y < tb_y + tb_h && y < ch; y++) {
if (tb_x < cw) pixels[y * cw + tb_x] = border_px;
if (tb_x + tb_w - 1 < cw) pixels[y * cw + (tb_x + tb_w - 1)] = border_px;
}
// TextBox background + border
c.fill_rect(tb_x, tb_y, tb_w, tb_h, colors::WHITE);
c.rect(tb_x, tb_y, tb_w, tb_h, colors::BORDER);
// Search text
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
draw_text_to_pixels(pixels, cw, ch, tb_x + 4, tb_y + (tb_h - FONT_HEIGHT) / 2,
ws->searchQuery, text_px);
c.text(tb_x + 4, tb_y + (tb_h - FONT_HEIGHT) / 2, ws->searchQuery, colors::TEXT_COLOR);
// ---- Search button ----
int btn_x = tb_x + tb_w + 6;
int btn_y = tb_y;
int btn_w = 66;
int btn_h = tb_h;
uint32_t accent_px = colors::ACCENT.to_pixel();
for (int y = btn_y; y < btn_y + btn_h && y < ch; y++)
for (int x = btn_x; x < btn_x + btn_w && x < cw; x++)
pixels[y * cw + x] = accent_px;
// Button text
uint32_t white_text = colors::WHITE.to_pixel();
const char* btnLabel = "Search";
int labelW = zenith::slen(btnLabel) * FONT_WIDTH;
draw_text_to_pixels(pixels, cw, ch,
btn_x + (btn_w - labelW) / 2,
btn_y + (btn_h - FONT_HEIGHT) / 2,
btnLabel, white_text);
c.button(btn_x, btn_y, btn_w, btn_h, "Search", colors::ACCENT, colors::WHITE, 0);
// ---- Content area ----
int content_y = WIKI_TOOLBAR_H + 1;
int content_h = ch - content_y;
int content_h = c.h - content_y;
int visibleLines = content_h / (FONT_HEIGHT + 4);
if (visibleLines < 1) visibleLines = 1;
if (ws->mode == WikiState::FETCHING) {
draw_text_to_pixels(pixels, cw, ch, 16, content_y + 16,
"Loading...", Color::from_rgb(0x88, 0x88, 0x88).to_pixel());
c.text(16, content_y + 16, "Loading...", Color::from_rgb(0x88, 0x88, 0x88));
} else if (ws->mode == WikiState::WIKI_ERROR) {
draw_text_to_pixels(pixels, cw, ch, 16, content_y + 16,
ws->statusMsg, colors::CLOSE_BTN.to_pixel());
c.text(16, content_y + 16, ws->statusMsg, colors::CLOSE_BTN);
} else if (ws->mode == WikiState::IDLE) {
draw_text_to_pixels(pixels, cw, ch, 16, content_y + 16,
"Type a topic and press Enter or click Search.",
Color::from_rgb(0x88, 0x88, 0x88).to_pixel());
c.text(16, content_y + 16,
"Type a topic and press Enter or click Search.",
Color::from_rgb(0x88, 0x88, 0x88));
} else if (ws->mode == WikiState::DONE && ws->lineCount > 0) {
int y = content_y + 8;
int lineH = FONT_HEIGHT + 4;
for (int i = ws->scrollY; i < ws->lineCount && y + FONT_HEIGHT < ch; i++) {
for (int i = ws->scrollY; i < ws->lineCount && y + FONT_HEIGHT < c.h; i++) {
WikiDisplayLine* dl = &ws->lines[i];
if (dl->text[0] != '\0') {
draw_text_to_pixels(pixels, cw, ch, 12, y, dl->text, dl->color);
c.text(12, y, dl->text, dl->color);
}
y += lineH;
}
// ---- Scrollbar ----
if (ws->lineCount > visibleLines) {
int sb_x = cw - WIKI_SCROLLBAR_W;
int sb_x = c.w - WIKI_SCROLLBAR_W;
int sb_y = content_y;
int sb_h = content_h;
// Scrollbar track
uint32_t sb_bg = colors::SCROLLBAR_BG.to_pixel();
for (int y2 = sb_y; y2 < sb_y + sb_h && y2 < ch; y2++)
for (int x = sb_x; x < cw; x++)
pixels[y2 * cw + x] = sb_bg;
c.fill_rect(sb_x, sb_y, WIKI_SCROLLBAR_W, sb_h, colors::SCROLLBAR_BG);
// Thumb
int maxScroll = ws->lineCount - visibleLines;
@@ -393,10 +350,7 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) {
if (thumbH < 20) thumbH = 20;
int thumbY = sb_y + (ws->scrollY * (sb_h - thumbH)) / maxScroll;
uint32_t sb_fg = colors::SCROLLBAR_FG.to_pixel();
for (int y2 = thumbY; y2 < thumbY + thumbH && y2 < ch; y2++)
for (int x = sb_x + 2; x < cw - 2; x++)
pixels[y2 * cw + x] = sb_fg;
c.fill_rect(sb_x + 2, thumbY, WIKI_SCROLLBAR_W - 4, thumbH, colors::SCROLLBAR_FG);
}
}
}
+3 -83
View File
@@ -16,6 +16,7 @@
#include <gui/svg.hpp>
#include <gui/widgets.hpp>
#include <gui/window.hpp>
#include <gui/canvas.hpp>
#include <gui/terminal.hpp>
#include <gui/desktop.hpp>
@@ -128,53 +129,6 @@ inline void format_mac(char* buf, const uint8_t* mac) {
(unsigned)mac[3], (unsigned)mac[4], (unsigned)mac[5]);
}
// ============================================================================
// Text rendering to pixel buffers (for app content areas)
// ============================================================================
inline void draw_text_to_pixels(uint32_t* pixels, int pw, int ph,
int tx, int ty, const char* text, uint32_t color_px) {
for (int i = 0; text[i] && tx + (i + 1) * FONT_WIDTH <= pw; i++) {
const uint8_t* glyph = &font_data[(unsigned char)text[i] * FONT_HEIGHT];
int cx = tx + i * FONT_WIDTH;
for (int fy = 0; fy < FONT_HEIGHT && ty + fy < ph; fy++) {
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx;
int dy = ty + fy;
if (dx >= 0 && dx < pw && dy >= 0 && dy < ph)
pixels[dy * pw + dx] = color_px;
}
}
}
}
}
inline void draw_text_to_pixels_2x(uint32_t* pixels, int pw, int ph,
int tx, int ty, const char* text, uint32_t color_px) {
for (int i = 0; text[i] && tx + (i + 1) * FONT_WIDTH * 2 <= pw; i++) {
const uint8_t* glyph = &font_data[(unsigned char)text[i] * FONT_HEIGHT];
int cx = tx + i * FONT_WIDTH * 2;
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx * 2;
int dy = ty + fy * 2;
for (int sy = 0; sy < 2; sy++)
for (int sx = 0; sx < 2; sx++) {
int px = dx + sx;
int py = dy + sy;
if (px >= 0 && px < pw && py >= 0 && py < ph)
pixels[py * pw + px] = color_px;
}
}
}
}
}
}
// ============================================================================
// File size formatting
// ============================================================================
@@ -201,42 +155,6 @@ inline void format_size(char* buf, int size) {
}
}
// ============================================================================
// Icon rendering to pixel buffer
// ============================================================================
inline void blit_icon_to_pixels(uint32_t* pixels, int pw, int ph,
int ix, int iy, const SvgIcon& icon) {
if (!icon.pixels) return;
for (int row = 0; row < icon.height; row++) {
int dy = iy + row;
if (dy < 0 || dy >= ph) continue;
for (int col = 0; col < icon.width; col++) {
int dx = ix + col;
if (dx < 0 || dx >= pw) continue;
uint32_t src = icon.pixels[row * icon.width + col];
uint8_t sa = (src >> 24) & 0xFF;
if (sa == 0) continue;
if (sa == 255) {
pixels[dy * pw + dx] = src;
} else {
uint32_t dst = pixels[dy * pw + dx];
uint8_t sr = (src >> 16) & 0xFF;
uint8_t sg = (src >> 8) & 0xFF;
uint8_t sb = src & 0xFF;
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = dst & 0xFF;
uint32_t a = sa, inv_a = 255 - sa;
uint32_t rr = (a * sr + inv_a * dr + 128) / 255;
uint32_t gg = (a * sg + inv_a * dg + 128) / 255;
uint32_t bb = (a * sb + inv_a * db + 128) / 255;
pixels[dy * pw + dx] = 0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
}
// ============================================================================
// Forward declarations for app launchers
// ============================================================================
@@ -249,3 +167,5 @@ void open_texteditor(DesktopState* ds);
void open_texteditor_with_file(DesktopState* ds, const char* path);
void open_klog(DesktopState* ds);
void open_wiki(DesktopState* ds);
void open_settings(DesktopState* ds);
void open_reboot_dialog(DesktopState* ds);
+424 -8
View File
@@ -55,6 +55,13 @@ void gui::desktop_init(DesktopState* ds) {
ds->icon_file_lg = svg_load("0:/icons/text-x-generic.svg", 48, 48, defColor);
ds->icon_exec_lg = svg_load("0:/icons/utilities-terminal.svg", 48, 48, defColor);
ds->icon_settings = svg_load("0:/icons/help-about.svg", 20, 20, defColor);
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
ds->ctx_menu_open = false;
ds->ctx_menu_x = 0;
ds->ctx_menu_y = 0;
ds->net_popup_open = false;
zenith::get_netcfg(&ds->cached_net_cfg);
ds->net_cfg_last_poll = zenith::get_milliseconds();
@@ -207,15 +214,17 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) {
}
draw_text(fb, title_x, title_y, win->title, colors::TEXT_COLOR);
// Call app draw callback to render content
if (win->on_draw) {
// Call app draw callback to render content (skip during resize — buffer is old size)
if (win->on_draw && !win->resizing) {
win->on_draw(win, fb);
}
// Blit content buffer to framebuffer
// Blit content buffer to framebuffer (clip to actual buffer size during resize)
Rect cr = win->content_rect();
if (win->content) {
fb.blit(cr.x, cr.y, cr.w, cr.h, win->content);
int blit_w = cr.w < win->content_w ? cr.w : win->content_w;
int blit_h = cr.h < win->content_h ? cr.h : win->content_h;
fb.blit(cr.x, cr.y, blit_w, blit_h, win->content);
}
}
@@ -340,7 +349,7 @@ void gui::desktop_draw_panel(DesktopState* ds) {
// App Menu (5 items with separator and rounded corners)
// ============================================================================
static constexpr int MENU_ITEM_COUNT = 7;
static constexpr int MENU_ITEM_COUNT = 9;
static constexpr int MENU_W = 220;
static constexpr int MENU_ITEM_H = 40;
@@ -371,6 +380,8 @@ static void desktop_draw_app_menu(DesktopState* ds) {
{ "Text Editor", &ds->icon_texteditor },
{ "Kernel Log", &ds->icon_terminal },
{ "Wikipedia", &ds->icon_wikipedia },
{ "About", &ds->icon_settings },
{ "Reboot", &ds->icon_reboot },
};
int mx = ds->mouse.x;
@@ -379,8 +390,8 @@ static void desktop_draw_app_menu(DesktopState* ds) {
for (int i = 0; i < MENU_ITEM_COUNT; i++) {
int iy = menu_y + 4 + i * MENU_ITEM_H;
// Thin separator line after System Info (before utility apps)
if (i == 3) {
// Thin separator lines before utility apps and before Settings
if (i == 3 || i == 7) {
int sep_y = iy - 1;
for (int sx = menu_x + 8; sx < menu_x + MENU_W - 8; sx++)
fb.put_pixel(sx, sep_y, colors::BORDER);
@@ -459,6 +470,164 @@ static void desktop_draw_net_popup(DesktopState* ds) {
draw_text(fb, tx, ty, line, colors::TEXT_COLOR);
}
// ============================================================================
// Reboot Dialog
// ============================================================================
struct RebootDialogState {
DesktopState* ds;
// Button layout (pixel-buffer-relative coordinates)
int btn_w, btn_h, btn_y, reboot_x, cancel_x;
bool hover_reboot, hover_cancel;
};
static void reboot_dialog_on_draw(Window* win, Framebuffer& fb) {
RebootDialogState* rs = (RebootDialogState*)win->app_data;
if (!rs) return;
Canvas c(win);
c.fill(colors::WINDOW_BG);
// "Reboot the system?" centered
const char* msg = "Reboot the system?";
int tw = text_width(msg);
c.text((c.w - tw) / 2, 30, msg, colors::TEXT_COLOR);
// Compute button layout
int btn_w = 100;
int btn_h = 32;
int btn_y = c.h - btn_h - 20;
int gap = 20;
int total_w = btn_w * 2 + gap;
int bx = (c.w - total_w) / 2;
rs->btn_w = btn_w;
rs->btn_h = btn_h;
rs->btn_y = btn_y;
rs->reboot_x = bx;
rs->cancel_x = bx + btn_w + gap;
// Draw Reboot button
Color reboot_bg = rs->hover_reboot
? Color::from_rgb(0xDD, 0x44, 0x44)
: Color::from_rgb(0xCC, 0x33, 0x33);
c.button(rs->reboot_x, btn_y, btn_w, btn_h, "Reboot", reboot_bg, colors::WHITE, 4);
// Draw Cancel button
Color cancel_bg = rs->hover_cancel
? Color::from_rgb(0x99, 0x99, 0x99)
: Color::from_rgb(0x88, 0x88, 0x88);
c.button(rs->cancel_x, btn_y, btn_w, btn_h, "Cancel", cancel_bg, colors::WHITE, 4);
}
static void reboot_dialog_on_mouse(Window* win, MouseEvent& ev) {
RebootDialogState* rs = (RebootDialogState*)win->app_data;
if (!rs) return;
Rect cr = win->content_rect();
int lx = ev.x - cr.x;
int ly = ev.y - cr.y;
Rect rb = {rs->reboot_x, rs->btn_y, rs->btn_w, rs->btn_h};
Rect cb = {rs->cancel_x, rs->btn_y, rs->btn_w, rs->btn_h};
rs->hover_reboot = rb.contains(lx, ly);
rs->hover_cancel = cb.contains(lx, ly);
if (ev.left_pressed()) {
if (rs->hover_reboot) zenith::reset();
if (rs->hover_cancel) {
for (int i = 0; i < rs->ds->window_count; i++) {
if (rs->ds->windows[i].app_data == rs) {
desktop_close_window(rs->ds, i);
return;
}
}
}
}
}
static void reboot_dialog_on_key(Window* win, const Zenith::KeyEvent& key) {
RebootDialogState* rs = (RebootDialogState*)win->app_data;
if (!rs || !key.pressed) return;
if (key.ascii == '\n' || key.ascii == '\r') {
zenith::reset();
}
if (key.scancode == 0x01) { // Escape
for (int i = 0; i < rs->ds->window_count; i++) {
if (rs->ds->windows[i].app_data == rs) {
desktop_close_window(rs->ds, i);
return;
}
}
}
}
static void reboot_dialog_on_close(Window* win) {
if (win->app_data) {
zenith::mfree(win->app_data);
win->app_data = nullptr;
}
}
void open_reboot_dialog(DesktopState* ds) {
int wx = (ds->screen_w - 300) / 2;
int wy = (ds->screen_h - 150) / 2;
int idx = desktop_create_window(ds, "Reboot", wx, wy, 300, 150);
if (idx < 0) return;
Window* win = &ds->windows[idx];
RebootDialogState* rs = (RebootDialogState*)zenith::malloc(sizeof(RebootDialogState));
zenith::memset(rs, 0, sizeof(RebootDialogState));
rs->ds = ds;
win->app_data = rs;
win->on_draw = reboot_dialog_on_draw;
win->on_mouse = reboot_dialog_on_mouse;
win->on_key = reboot_dialog_on_key;
win->on_close = reboot_dialog_on_close;
}
static gui::ResizeEdge hit_test_resize_edge(const gui::Rect& f, int mx, int my) {
using namespace gui;
int G = RESIZE_GRAB;
if (!f.contains(mx, my)) return RESIZE_NONE;
bool near_left = mx < f.x + G;
bool near_right = mx >= f.x + f.w - G;
bool near_top = my < f.y + G;
bool near_bottom = my >= f.y + f.h - G;
// Corners first
if (near_top && near_left) return RESIZE_TOP_LEFT;
if (near_top && near_right) return RESIZE_TOP_RIGHT;
if (near_bottom && near_left) return RESIZE_BOTTOM_LEFT;
if (near_bottom && near_right) return RESIZE_BOTTOM_RIGHT;
// Edges
if (near_top) return RESIZE_TOP;
if (near_bottom) return RESIZE_BOTTOM;
if (near_left) return RESIZE_LEFT;
if (near_right) return RESIZE_RIGHT;
return RESIZE_NONE;
}
static gui::CursorStyle cursor_for_edge(gui::ResizeEdge edge) {
using namespace gui;
switch (edge) {
case RESIZE_LEFT: case RESIZE_RIGHT:
return CURSOR_RESIZE_H;
case RESIZE_TOP: case RESIZE_BOTTOM:
return CURSOR_RESIZE_V;
case RESIZE_TOP_LEFT: case RESIZE_BOTTOM_RIGHT:
return CURSOR_RESIZE_NWSE;
case RESIZE_TOP_RIGHT: case RESIZE_BOTTOM_LEFT:
return CURSOR_RESIZE_NESW;
default:
return CURSOR_ARROW;
}
}
void gui::desktop_compose(DesktopState* ds) {
Framebuffer& fb = ds->fb;
@@ -508,8 +677,91 @@ void gui::desktop_compose(DesktopState* ds) {
desktop_draw_net_popup(ds);
}
// Draw right-click context menu if open
if (ds->ctx_menu_open) {
static constexpr int CTX_MENU_W = 180;
static constexpr int CTX_ITEM_H = 36;
static constexpr int CTX_ITEM_COUNT = 4;
int cmx = ds->ctx_menu_x;
int cmy = ds->ctx_menu_y;
int cmh = CTX_ITEM_H * CTX_ITEM_COUNT + 8;
// Clamp to screen
if (cmx + CTX_MENU_W > sw) cmx = sw - CTX_MENU_W;
if (cmy + cmh > sh) cmy = sh - cmh;
draw_shadow(fb, cmx, cmy, CTX_MENU_W, cmh, 4, colors::SHADOW);
fill_rounded_rect(fb, cmx, cmy, CTX_MENU_W, cmh, 8, colors::MENU_BG);
draw_rect(fb, cmx, cmy, CTX_MENU_W, cmh, colors::BORDER);
struct CtxItem { const char* label; SvgIcon* icon; };
CtxItem ctx_items[CTX_ITEM_COUNT] = {
{ "Terminal", &ds->icon_terminal },
{ "Files", &ds->icon_filemanager },
{ "About", &ds->icon_settings },
{ "Reboot", &ds->icon_reboot },
};
int mmx = ds->mouse.x;
int mmy = ds->mouse.y;
for (int i = 0; i < CTX_ITEM_COUNT; i++) {
int iy = cmy + 4 + i * CTX_ITEM_H;
Rect item_r = {cmx + 4, iy, CTX_MENU_W - 8, CTX_ITEM_H};
if (item_r.contains(mmx, mmy)) {
fill_rounded_rect(fb, item_r.x, item_r.y, item_r.w, item_r.h, 4, colors::MENU_HOVER);
}
int icon_x = item_r.x + 8;
int icon_y = item_r.y + (CTX_ITEM_H - 20) / 2;
if (ctx_items[i].icon && ctx_items[i].icon->pixels) {
fb.blit_alpha(icon_x, icon_y, ctx_items[i].icon->width, ctx_items[i].icon->height, ctx_items[i].icon->pixels);
}
int tx = icon_x + 28;
int ty = item_r.y + (CTX_ITEM_H - FONT_HEIGHT) / 2;
draw_text(fb, tx, ty, ctx_items[i].label, colors::TEXT_COLOR);
}
}
// Draw snap preview overlay while dragging to screen edge
for (int i = 0; i < ds->window_count; i++) {
Window* win = &ds->windows[i];
if (win->dragging) {
int dmx = ds->mouse.x;
if (dmx <= 0) {
fb.fill_rect_alpha(0, PANEL_HEIGHT, sw / 2, sh - PANEL_HEIGHT,
Color::from_rgba(0x33, 0x77, 0xCC, 0x30));
} else if (dmx >= sw - 1) {
fb.fill_rect_alpha(sw / 2, PANEL_HEIGHT, sw / 2, sh - PANEL_HEIGHT,
Color::from_rgba(0x33, 0x77, 0xCC, 0x30));
}
break;
}
}
// Determine cursor style based on resize hover or active resize
CursorStyle cur_style = CURSOR_ARROW;
for (int i = ds->window_count - 1; i >= 0; i--) {
Window* win = &ds->windows[i];
if (win->resizing) {
cur_style = cursor_for_edge(win->resize_edge);
break;
}
if (win->state == WIN_MINIMIZED || win->state == WIN_CLOSED || win->state == WIN_MAXIMIZED)
continue;
if (win->frame.contains(ds->mouse.x, ds->mouse.y)) {
ResizeEdge edge = hit_test_resize_edge(win->frame, ds->mouse.x, ds->mouse.y);
if (edge != RESIZE_NONE) {
cur_style = cursor_for_edge(edge);
}
break;
}
}
// Draw cursor last
draw_cursor(fb, ds->mouse.x, ds->mouse.y);
draw_cursor(fb, ds->mouse.x, ds->mouse.y, cur_style);
}
void gui::desktop_handle_mouse(DesktopState* ds) {
@@ -520,6 +772,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
bool left_pressed = (buttons & 0x01) && !(prev & 0x01);
bool left_held = (buttons & 0x01);
bool left_released = !(buttons & 0x01) && (prev & 0x01);
bool right_pressed = (buttons & 0x02) && !(prev & 0x02);
MouseEvent ev;
ev.x = mx;
@@ -528,6 +781,42 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ev.prev_buttons = prev;
ev.scroll = ds->mouse.scrollDelta;
// Handle context menu clicks
if (ds->ctx_menu_open) {
if (left_pressed) {
static constexpr int CTX_MENU_W = 180;
static constexpr int CTX_ITEM_H = 36;
static constexpr int CTX_ITEM_COUNT = 4;
int cmx = ds->ctx_menu_x;
int cmy = ds->ctx_menu_y;
int cmh = CTX_ITEM_H * CTX_ITEM_COUNT + 8;
if (cmx + CTX_MENU_W > ds->screen_w) cmx = ds->screen_w - CTX_MENU_W;
if (cmy + cmh > ds->screen_h) cmy = ds->screen_h - cmh;
Rect ctx_rect = {cmx, cmy, CTX_MENU_W, cmh};
if (ctx_rect.contains(mx, my)) {
int rel_y = my - cmy - 4;
int item_idx = rel_y / CTX_ITEM_H;
if (item_idx >= 0 && item_idx < CTX_ITEM_COUNT) {
ds->ctx_menu_open = false;
switch (item_idx) {
case 0: open_terminal(ds); break;
case 1: open_filemanager(ds); break;
case 2: open_settings(ds); break;
case 3: open_reboot_dialog(ds); break;
}
return;
}
}
ds->ctx_menu_open = false;
return;
}
if (right_pressed) {
ds->ctx_menu_open = false;
return;
}
}
// Check for ongoing window drags first
for (int i = 0; i < ds->window_count; i++) {
Window* win = &ds->windows[i];
@@ -542,6 +831,88 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
}
if (left_released) {
win->dragging = false;
// Window edge snapping
if (mx <= 0) {
win->saved_frame = win->frame;
win->frame = {0, PANEL_HEIGHT, ds->screen_w / 2, ds->screen_h - PANEL_HEIGHT};
win->state = WIN_MAXIMIZED;
Rect cr = win->content_rect();
if (cr.w != win->content_w || cr.h != win->content_h) {
if (win->content) zenith::free(win->content);
win->content_w = cr.w;
win->content_h = cr.h;
win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4);
zenith::memset(win->content, 0xFF, cr.w * cr.h * 4);
}
} else if (mx >= ds->screen_w - 1) {
win->saved_frame = win->frame;
win->frame = {ds->screen_w / 2, PANEL_HEIGHT, ds->screen_w / 2, ds->screen_h - PANEL_HEIGHT};
win->state = WIN_MAXIMIZED;
Rect cr = win->content_rect();
if (cr.w != win->content_w || cr.h != win->content_h) {
if (win->content) zenith::free(win->content);
win->content_w = cr.w;
win->content_h = cr.h;
win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4);
zenith::memset(win->content, 0xFF, cr.w * cr.h * 4);
}
}
}
return;
}
}
// Check for ongoing window resizes
for (int i = 0; i < ds->window_count; i++) {
Window* win = &ds->windows[i];
if (win->resizing) {
if (left_held) {
int dx = mx - win->resize_start_mx;
int dy = my - win->resize_start_my;
Rect sf = win->resize_start_frame;
ResizeEdge edge = win->resize_edge;
int new_x = sf.x, new_y = sf.y, new_w = sf.w, new_h = sf.h;
if (edge == RESIZE_RIGHT || edge == RESIZE_TOP_RIGHT || edge == RESIZE_BOTTOM_RIGHT)
new_w = sf.w + dx;
if (edge == RESIZE_BOTTOM || edge == RESIZE_BOTTOM_LEFT || edge == RESIZE_BOTTOM_RIGHT)
new_h = sf.h + dy;
if (edge == RESIZE_LEFT || edge == RESIZE_TOP_LEFT || edge == RESIZE_BOTTOM_LEFT) {
new_x = sf.x + dx;
new_w = sf.w - dx;
}
if (edge == RESIZE_TOP || edge == RESIZE_TOP_LEFT || edge == RESIZE_TOP_RIGHT) {
new_y = sf.y + dy;
new_h = sf.h - dy;
}
// Enforce minimum size
if (new_w < MIN_WINDOW_W) {
if (edge == RESIZE_LEFT || edge == RESIZE_TOP_LEFT || edge == RESIZE_BOTTOM_LEFT)
new_x = sf.x + sf.w - MIN_WINDOW_W;
new_w = MIN_WINDOW_W;
}
if (new_h < MIN_WINDOW_H) {
if (edge == RESIZE_TOP || edge == RESIZE_TOP_LEFT || edge == RESIZE_TOP_RIGHT)
new_y = sf.y + sf.h - MIN_WINDOW_H;
new_h = MIN_WINDOW_H;
}
win->frame = {new_x, new_y, new_w, new_h};
}
if (left_released) {
win->resizing = false;
// Reallocate content buffer if dimensions changed
Rect cr = win->content_rect();
if (cr.w != win->content_w || cr.h != win->content_h) {
if (win->content) zenith::free(win->content);
win->content_w = cr.w;
win->content_h = cr.h;
win->content = (uint32_t*)zenith::alloc(cr.w * cr.h * 4);
zenith::memset(win->content, 0xFF, cr.w * cr.h * 4);
}
win->dirty = true;
}
return;
}
@@ -566,6 +937,8 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
case 4: open_texteditor(ds); break;
case 5: open_klog(ds); break;
case 6: open_wiki(ds); break;
case 7: open_settings(ds); break;
case 8: open_reboot_dialog(ds); break;
}
ds->app_menu_open = false;
}
@@ -597,6 +970,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
if (mx < 36) {
ds->app_menu_open = !ds->app_menu_open;
ds->net_popup_open = false;
ds->ctx_menu_open = false;
return;
}
@@ -604,6 +978,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
if (ds->net_icon_rect.w > 0 && ds->net_icon_rect.contains(mx, my)) {
ds->net_popup_open = !ds->net_popup_open;
ds->app_menu_open = false;
ds->ctx_menu_open = false;
return;
}
// Window indicator buttons
@@ -683,6 +1058,26 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
return;
}
// Check resize edges (before titlebar drag, so corner grabs work)
if (win->state != WIN_MAXIMIZED) {
ResizeEdge edge = hit_test_resize_edge(win->frame, mx, my);
if (edge != RESIZE_NONE) {
win->resizing = true;
win->resize_edge = edge;
win->resize_start_frame = win->frame;
win->resize_start_mx = mx;
win->resize_start_my = my;
desktop_raise_window(ds, i);
int new_idx = ds->window_count - 1;
ds->windows[new_idx].resizing = true;
ds->windows[new_idx].resize_edge = edge;
ds->windows[new_idx].resize_start_frame = ds->windows[new_idx].frame;
ds->windows[new_idx].resize_start_mx = mx;
ds->windows[new_idx].resize_start_my = my;
return;
}
}
// Check titlebar (start drag)
Rect tb = win->titlebar_rect();
if (tb.contains(mx, my)) {
@@ -718,6 +1113,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
}
ds->app_menu_open = false;
ds->ctx_menu_open = false;
}
// Handle scroll events on focused window
@@ -728,6 +1124,26 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
win->on_mouse(win, ev);
}
}
// Right-click on desktop background opens context menu
if (right_pressed && my >= PANEL_HEIGHT) {
bool on_window = false;
for (int i = ds->window_count - 1; i >= 0; i--) {
Window* win = &ds->windows[i];
if (win->state == WIN_MINIMIZED || win->state == WIN_CLOSED) continue;
if (win->frame.contains(mx, my)) {
on_window = true;
break;
}
}
if (!on_window) {
ds->ctx_menu_open = true;
ds->ctx_menu_x = mx;
ds->ctx_menu_y = my;
ds->app_menu_open = false;
ds->net_popup_open = false;
}
}
}
void gui::desktop_handle_keyboard(DesktopState* ds, const Zenith::KeyEvent& key) {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.