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
View File
@@ -11,3 +11,4 @@ programs/bin/
programs/obj/ programs/obj/
programs/src/doom/obj/ programs/src/doom/obj/
programs/gui/icons/ programs/gui/icons/
programs/src/desktop/obj/
+3 -2
View File
@@ -161,7 +161,7 @@ namespace Drivers::PS2::Mouse {
g_State.X += deltaX; g_State.X += deltaX;
g_State.Y += deltaY; g_State.Y += deltaY;
g_State.Buttons = buttons; g_State.Buttons = buttons;
g_State.ScrollDelta = scrollDelta; g_State.ScrollDelta += scrollDelta;
// Clamp to screen bounds // Clamp to screen bounds
if (g_State.X < 0) g_State.X = 0; if (g_State.X < 0) g_State.X = 0;
@@ -175,6 +175,7 @@ namespace Drivers::PS2::Mouse {
MouseState GetMouseState() { MouseState GetMouseState() {
g_StateLock.Acquire(); g_StateLock.Acquire();
MouseState state = g_State; MouseState state = g_State;
g_State.ScrollDelta = 0; // clear after read so deltas don't repeat
g_StateLock.Release(); g_StateLock.Release();
return state; return state;
} }
@@ -202,7 +203,7 @@ namespace Drivers::PS2::Mouse {
g_State.X += (int32_t)deltaX; g_State.X += (int32_t)deltaX;
g_State.Y += (int32_t)deltaY; g_State.Y += (int32_t)deltaY;
g_State.Buttons = buttons; g_State.Buttons = buttons;
g_State.ScrollDelta = (int32_t)scroll; g_State.ScrollDelta += (int32_t)scroll;
// Clamp to screen bounds // Clamp to screen bounds
if (g_State.X < 0) g_State.X = 0; if (g_State.X < 0) g_State.X = 0;
+199
View File
@@ -0,0 +1,199 @@
/*
* canvas.hpp
* ZenithOS Canvas — drawing primitives for pixel buffer (uint32_t*) targets
* Mirrors Framebuffer API but operates directly on app content buffers.
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/font.hpp"
#include "gui/svg.hpp"
#include "gui/window.hpp"
namespace gui {
struct Canvas {
uint32_t* pixels;
int w, h;
// ---- Constructors ----
Canvas(uint32_t* px, int width, int height)
: pixels(px), w(width), h(height) {}
Canvas(Window* win)
: pixels(win->content), w(win->content_w), h(win->content_h) {}
// ---- Core drawing ----
void fill(Color c) {
uint32_t px = c.to_pixel();
int total = w * h;
for (int i = 0; i < total; i++) pixels[i] = px;
}
void put_pixel(int x, int y, Color c) {
if (x >= 0 && x < w && y >= 0 && y < h)
pixels[y * w + x] = c.to_pixel();
}
void fill_rect(int x, int y, int rw, int rh, Color c) {
uint32_t px = c.to_pixel();
int x0 = gui_max(x, 0), y0 = gui_max(y, 0);
int x1 = gui_min(x + rw, w), y1 = gui_min(y + rh, h);
for (int dy = y0; dy < y1; dy++)
for (int dx = x0; dx < x1; dx++)
pixels[dy * w + dx] = px;
}
void fill_rounded_rect(int x, int y, int rw, int rh, int radius, Color c) {
if (radius <= 0) { fill_rect(x, y, rw, rh, c); return; }
uint32_t px = c.to_pixel();
for (int row = 0; row < rh; row++) {
int dy = y + row;
if (dy < 0 || dy >= h) continue;
for (int col = 0; col < rw; col++) {
int dx = x + col;
if (dx < 0 || dx >= w) continue;
bool in_corner = false;
int cx_off = 0, cy_off = 0;
if (col < radius && row < radius) {
cx_off = radius - col; cy_off = radius - row; in_corner = true;
} else if (col >= rw - radius && row < radius) {
cx_off = col - (rw - radius - 1); cy_off = radius - row; in_corner = true;
} else if (col < radius && row >= rh - radius) {
cx_off = radius - col; cy_off = row - (rh - radius - 1); in_corner = true;
} else if (col >= rw - radius && row >= rh - radius) {
cx_off = col - (rw - radius - 1); cy_off = row - (rh - radius - 1); in_corner = true;
}
if (in_corner && cx_off * cx_off + cy_off * cy_off > radius * radius) continue;
pixels[dy * w + dx] = px;
}
}
}
void hline(int x, int y, int len, Color c) {
if (y < 0 || y >= h) return;
uint32_t px = c.to_pixel();
int x0 = gui_max(x, 0), x1 = gui_min(x + len, w);
for (int dx = x0; dx < x1; dx++)
pixels[y * w + dx] = px;
}
void vline(int x, int y, int len, Color c) {
if (x < 0 || x >= w) return;
uint32_t px = c.to_pixel();
int y0 = gui_max(y, 0), y1 = gui_min(y + len, h);
for (int dy = y0; dy < y1; dy++)
pixels[dy * w + x] = px;
}
void rect(int x, int y, int rw, int rh, Color c) {
hline(x, y, rw, c);
hline(x, y + rh - 1, rw, c);
vline(x, y, rh, c);
vline(x + rw - 1, y, rh, c);
}
// ---- Text ----
void text(int x, int y, const char* str, Color c) {
uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
int cx = x + i * FONT_WIDTH;
for (int fy = 0; fy < FONT_HEIGHT && y + fy < h; fy++) {
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx;
int dy = y + fy;
if (dx >= 0 && dx < w && dy >= 0)
pixels[dy * w + dx] = px;
}
}
}
}
}
void text_2x(int x, int y, const char* str, Color c) {
uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH * 2 <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
int cx = x + 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 = y + fy * 2;
for (int sy = 0; sy < 2; sy++)
for (int sx = 0; sx < 2; sx++) {
int pdx = dx + sx;
int pdy = dy + sy;
if (pdx >= 0 && pdx < w && pdy >= 0 && pdy < h)
pixels[pdy * w + pdx] = px;
}
}
}
}
}
}
// ---- Icons ----
void icon(int x, int y, const SvgIcon& ic) {
if (!ic.pixels) return;
for (int row = 0; row < ic.height; row++) {
int dy = y + row;
if (dy < 0 || dy >= h) continue;
for (int col = 0; col < ic.width; col++) {
int dx = x + col;
if (dx < 0 || dx >= w) continue;
uint32_t src = ic.pixels[row * ic.width + col];
uint8_t sa = (src >> 24) & 0xFF;
if (sa == 0) continue;
if (sa == 255) {
pixels[dy * w + dx] = src;
} else {
uint32_t dst = pixels[dy * w + 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 * w + dx] = 0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
}
// ---- High-level helpers ----
void kv_line(int x, int* y, const char* line, Color c, int line_h = FONT_HEIGHT + 6) {
text(x, *y, line, c);
*y += line_h;
}
void separator(int x_start, int x_end, int* y, Color c, int spacing = 8) {
hline(x_start, *y, x_end - x_start, c);
*y += spacing;
}
void button(int x, int y, int bw, int bh, const char* label,
Color bg, Color fg, int radius = 4) {
fill_rounded_rect(x, y, bw, bh, radius, bg);
int tw = text_width(label);
int tx = x + (bw - tw) / 2;
int ty = y + (bh - FONT_HEIGHT) / 2;
text(tx, ty, label, fg);
}
};
} // namespace gui
+6
View File
@@ -51,6 +51,12 @@ struct DesktopState {
SvgIcon icon_file_lg; SvgIcon icon_file_lg;
SvgIcon icon_exec_lg; SvgIcon icon_exec_lg;
SvgIcon icon_settings;
SvgIcon icon_reboot;
bool ctx_menu_open;
int ctx_menu_x, ctx_menu_y;
bool net_popup_open; bool net_popup_open;
Zenith::NetCfg cached_net_cfg; Zenith::NetCfg cached_net_cfg;
uint64_t net_cfg_last_poll; uint64_t net_cfg_last_poll;
+160 -5
View File
@@ -217,20 +217,175 @@ static constexpr uint16_t cursor_fill[16] = {
0x0000, // row 15: no fill 0x0000, // row 15: no fill
}; };
// Resize cursor: horizontal double arrow (left-right)
static constexpr uint16_t cursor_h_resize_outline[16] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0820, // 0000100000100000
0x1830, // 0001100000110000
0x3FF8, // 0011111111111000
0x7FFC, // 0111111111111100
0x3FF8, // 0011111111111000
0x1830, // 0001100000110000
0x0820, // 0000100000100000
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
static constexpr uint16_t cursor_h_resize_fill[16] = {
0x0000, 0x0000, 0x0000, 0x0000,
0x0000,
0x0000,
0x1FF0, // 0001111111110000
0x3FF8, // 0011111111111000
0x1FF0, // 0001111111110000
0x0000,
0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
};
// Resize cursor: vertical double arrow (up-down)
static constexpr uint16_t cursor_v_resize_outline[16] = {
0x0000, 0x0000,
0x0200, // 0000001000000000
0x0700, // 0000011100000000
0x0F80, // 0000111110000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0F80, // 0000111110000000
0x0700, // 0000011100000000
0x0200, // 0000001000000000
0x0000, 0x0000,
};
static constexpr uint16_t cursor_v_resize_fill[16] = {
0x0000, 0x0000, 0x0000,
0x0200, // 0000001000000000
0x0700, // 0000011100000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0200, // 0000001000000000
0x0700, // 0000011100000000
0x0200, // 0000001000000000
0x0000, 0x0000, 0x0000,
};
// Resize cursor: diagonal NW-SE double arrow
static constexpr uint16_t cursor_nwse_resize_outline[16] = {
0x0000, 0x0000,
0x7C00, // 0111110000000000
0x6000, // 0110000000000000
0x5000, // 0101000000000000
0x4800, // 0100100000000000
0x2400, // 0010010000000000
0x1200, // 0001001000000000
0x0900, // 0000100100000000
0x0480, // 0000010010000000
0x0240, // 0000001001000000
0x0140, // 0000000101000000
0x00C0, // 0000000011000000
0x07C0, // 0000011111000000
0x0000, 0x0000,
};
static constexpr uint16_t cursor_nwse_resize_fill[16] = {
0x0000, 0x0000, 0x0000,
0x1C00, // 0001110000000000
0x2800, // 0010100000000000
0x0400, // 0000010000000000
0x0200, // 0000001000000000
0x0100, // 0000000100000000
0x0080, // 0000000010000000
0x0040, // 0000000001000000
0x0280, // 0000001010000000
0x0380, // 0000001110000000
0x0000, 0x0000, 0x0000, 0x0000,
};
// Resize cursor: diagonal NE-SW double arrow
static constexpr uint16_t cursor_nesw_resize_outline[16] = {
0x0000, 0x0000,
0x07C0, // 0000011111000000
0x00C0, // 0000000011000000
0x0140, // 0000000101000000
0x0240, // 0000001001000000
0x0480, // 0000010010000000
0x0900, // 0000100100000000
0x1200, // 0001001000000000
0x2400, // 0010010000000000
0x4800, // 0100100000000000
0x5000, // 0101000000000000
0x6000, // 0110000000000000
0x7C00, // 0111110000000000
0x0000, 0x0000,
};
static constexpr uint16_t cursor_nesw_resize_fill[16] = {
0x0000, 0x0000, 0x0000,
0x0380, // 0000001110000000
0x0280, // 0000001010000000
0x0040, // 0000000001000000
0x0080, // 0000000010000000
0x0100, // 0000000100000000
0x0200, // 0000001000000000
0x0400, // 0000010000000000
0x2800, // 0010100000000000
0x1C00, // 0001110000000000
0x0000, 0x0000, 0x0000, 0x0000,
};
enum CursorStyle {
CURSOR_ARROW = 0,
CURSOR_RESIZE_H, // left-right
CURSOR_RESIZE_V, // up-down
CURSOR_RESIZE_NWSE, // diagonal NW-SE
CURSOR_RESIZE_NESW, // diagonal NE-SW
};
// Draw the mouse cursor at (x, y) // Draw the mouse cursor at (x, y)
inline void draw_cursor(Framebuffer& fb, int x, int y) { inline void draw_cursor(Framebuffer& fb, int x, int y, CursorStyle style = CURSOR_ARROW) {
const uint16_t* outline_data = cursor_outline;
const uint16_t* fill_data = cursor_fill;
int ox = 0, oy = 0; // hotspot offset for centered cursors
switch (style) {
case CURSOR_RESIZE_H:
outline_data = cursor_h_resize_outline;
fill_data = cursor_h_resize_fill;
ox = -8; oy = -8;
break;
case CURSOR_RESIZE_V:
outline_data = cursor_v_resize_outline;
fill_data = cursor_v_resize_fill;
ox = -8; oy = -8;
break;
case CURSOR_RESIZE_NWSE:
outline_data = cursor_nwse_resize_outline;
fill_data = cursor_nwse_resize_fill;
ox = -8; oy = -8;
break;
case CURSOR_RESIZE_NESW:
outline_data = cursor_nesw_resize_outline;
fill_data = cursor_nesw_resize_fill;
ox = -8; oy = -8;
break;
default:
break;
}
Color black = colors::BLACK; Color black = colors::BLACK;
Color white = colors::WHITE; Color white = colors::WHITE;
for (int row = 0; row < 16; row++) { for (int row = 0; row < 16; row++) {
uint16_t outline = cursor_outline[row]; uint16_t outline = outline_data[row];
uint16_t fill = cursor_fill[row]; uint16_t fill = fill_data[row];
for (int col = 0; col < 16; col++) { for (int col = 0; col < 16; col++) {
uint16_t mask = (uint16_t)(0x8000 >> col); uint16_t mask = (uint16_t)(0x8000 >> col);
if (outline & mask) { if (outline & mask) {
fb.put_pixel(x + col, y + row, black); fb.put_pixel(x + ox + col, y + oy + row, black);
} else if (fill & mask) { } else if (fill & mask) {
fb.put_pixel(x + col, y + row, white); fb.put_pixel(x + ox + col, y + oy + row, white);
} }
} }
} }
+12
View File
@@ -14,11 +14,20 @@ namespace gui {
enum WindowState { WIN_NORMAL, WIN_MINIMIZED, WIN_MAXIMIZED, WIN_CLOSED }; enum WindowState { WIN_NORMAL, WIN_MINIMIZED, WIN_MAXIMIZED, WIN_CLOSED };
enum ResizeEdge {
RESIZE_NONE = 0,
RESIZE_LEFT, RESIZE_RIGHT, RESIZE_TOP, RESIZE_BOTTOM,
RESIZE_TOP_LEFT, RESIZE_TOP_RIGHT, RESIZE_BOTTOM_LEFT, RESIZE_BOTTOM_RIGHT
};
static constexpr int TITLEBAR_HEIGHT = 30; static constexpr int TITLEBAR_HEIGHT = 30;
static constexpr int BORDER_WIDTH = 1; static constexpr int BORDER_WIDTH = 1;
static constexpr int SHADOW_SIZE = 3; static constexpr int SHADOW_SIZE = 3;
static constexpr int BTN_RADIUS = 6; static constexpr int BTN_RADIUS = 6;
static constexpr int MAX_TITLE_LEN = 64; static constexpr int MAX_TITLE_LEN = 64;
static constexpr int RESIZE_GRAB = 6;
static constexpr int MIN_WINDOW_W = 120;
static constexpr int MIN_WINDOW_H = 80;
struct Window; struct Window;
using WindowDrawCallback = void (*)(Window* win, Framebuffer& fb); using WindowDrawCallback = void (*)(Window* win, Framebuffer& fb);
@@ -42,6 +51,9 @@ struct Window {
int drag_offset_x, drag_offset_y; int drag_offset_x, drag_offset_y;
bool resizing; bool resizing;
ResizeEdge resize_edge;
Rect resize_start_frame;
int resize_start_mx, resize_start_my;
Rect saved_frame; Rect saved_frame;
+1 -1
View File
@@ -63,7 +63,7 @@ LDFLAGS := \
# ---- Source files ---- # ---- 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)) OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
# ---- Target ---- # ---- Target ----
+12 -25
View File
@@ -222,29 +222,21 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) {
CalcState* cs = (CalcState*)win->app_data; CalcState* cs = (CalcState*)win->app_data;
if (!cs) return; if (!cs) return;
Rect cr = win->content_rect(); Canvas c(win);
int cw = cr.w;
int ch = cr.h;
uint32_t* pixels = win->content;
// Background // Background
uint32_t bg_px = Color::from_rgb(0xF0, 0xF0, 0xF0).to_pixel(); c.fill(Color::from_rgb(0xF0, 0xF0, 0xF0));
for (int i = 0; i < cw * ch; i++) pixels[i] = bg_px;
// Display area // Display area
uint32_t disp_px = Color::from_rgb(0x2D, 0x2D, 0x2D).to_pixel(); c.fill_rect(0, 0, c.w, CALC_DISPLAY_H, Color::from_rgb(0x2D, 0x2D, 0x2D));
for (int y = 0; y < CALC_DISPLAY_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = disp_px;
// Display text (right-aligned, 2x scale) // Display text (right-aligned, 2x scale)
uint32_t disp_text = colors::WHITE.to_pixel();
int text_len = zenith::slen(cs->display_str); int text_len = zenith::slen(cs->display_str);
int text_w = text_len * FONT_WIDTH * 2; 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; int ty = (CALC_DISPLAY_H - FONT_HEIGHT * 2) / 2;
if (tx < 4) tx = 4; 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 // Button grid
int grid_y = CALC_DISPLAY_H + CALC_BTN_PAD; int grid_y = CALC_DISPLAY_H + CALC_BTN_PAD;
@@ -264,30 +256,25 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) {
} }
// Button color // Button color
uint32_t btn_px; Color btn_color;
if (col == 3) { if (col == 3) {
// Operator column: accent blue btn_color = colors::ACCENT;
btn_px = colors::ACCENT.to_pixel();
} else if (row == 0) { } else if (row == 0) {
// Function row: gray btn_color = Color::from_rgb(0xD0, 0xD0, 0xD0);
btn_px = Color::from_rgb(0xD0, 0xD0, 0xD0).to_pixel();
} else { } else {
// Digit buttons: light gray btn_color = Color::from_rgb(0xE8, 0xE8, 0xE8);
btn_px = Color::from_rgb(0xE8, 0xE8, 0xE8).to_pixel();
} }
// Draw button // Draw button
for (int dy = 0; dy < CALC_BTN_H && by + dy < ch; dy++) c.fill_rect(bx, by, bw, CALC_BTN_H, btn_color);
for (int dx = 0; dx < bw && bx + dx < cw; dx++)
pixels[(by + dy) * cw + (bx + dx)] = btn_px;
// Button text // Button text
const char* label = calc_labels[row][col]; 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 label_w = zenith::slen(label) * FONT_WIDTH;
int lx = bx + (bw - label_w) / 2; int lx = bx + (bw - label_w) / 2;
int ly = by + (CALC_BTN_H - FONT_HEIGHT) / 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; FileManagerState* fm = (FileManagerState*)win->app_data;
if (!fm) return; if (!fm) return;
Rect cr = win->content_rect(); Canvas c(win);
int cw = cr.w; c.fill(colors::WINDOW_BG);
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();
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; DesktopState* ds = fm->desktop;
// ---- Toolbar (32px) ---- // ---- Toolbar (32px) ----
for (int y = 0; y < FM_TOOLBAR_H && y < ch; y++) c.fill_rect(0, 0, c.w, FM_TOOLBAR_H, toolbar_color);
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = toolbar_px;
// Toolbar buttons: Back, Forward, Up, Home // Toolbar buttons: Back, Forward, Up, Home
struct ToolBtn { int x; SvgIcon* icon; }; 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++) { for (int i = 0; i < 4; i++) {
int bx = btns[i].x; int bx = btns[i].x;
int by = 4; int by = 4;
// Button background c.fill_rect(bx, by, 24, 24, btn_bg);
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;
if (btns[i].icon) { if (btns[i].icon) {
int ix = bx + (24 - btns[i].icon->width) / 2; int ix = bx + (24 - btns[i].icon->width) / 2;
int iy = by + (24 - btns[i].icon->height) / 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) // Toggle view button (5th toolbar button)
{ {
int bx = 120, by = 4; int bx = 120, by = 4;
uint32_t btn_px = Color::from_rgb(0xE8, 0xE8, 0xE8).to_pixel(); c.fill_rect(bx, by, 24, 24, btn_bg);
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();
if (fm->grid_view) { if (fm->grid_view) {
// Draw 4 small squares to indicate grid mode // Draw 4 small squares to indicate grid mode
for (int r = 0; r < 2; r++) for (int r = 0; r < 2; r++)
for (int c = 0; c < 2; c++) for (int cc = 0; cc < 2; cc++)
for (int dy = 0; dy < 6; dy++) c.fill_rect(bx + 5 + cc * 8, by + 5 + r * 8, 6, 6, colors::TEXT_COLOR);
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;
} else { } else {
// Draw 3 horizontal lines to indicate list mode // Draw 3 horizontal lines to indicate list mode
for (int r = 0; r < 3; r++) for (int r = 0; r < 3; r++)
for (int dx = 0; dx < 14; dx++) c.fill_rect(bx + 5, by + 5 + r * 5, 14, 2, colors::TEXT_COLOR);
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;
} }
} }
// Toolbar separator // Toolbar separator
int sep_y = FM_TOOLBAR_H - 1; c.hline(0, FM_TOOLBAR_H - 1, c.w, colors::BORDER);
if (sep_y < ch) {
for (int x = 0; x < cw; x++)
pixels[sep_y * cw + x] = sep_px;
}
// ---- Path bar ---- // ---- Path bar ----
int pathbar_y = FM_TOOLBAR_H; int pathbar_y = FM_TOOLBAR_H;
uint32_t pathbar_px = Color::from_rgb(0xF0, 0xF0, 0xF0).to_pixel(); c.fill_rect(0, pathbar_y, c.w, FM_PATHBAR_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
for (int y = pathbar_y; y < pathbar_y + FM_PATHBAR_H && y < ch; y++) c.text(8, pathbar_y + 4, fm->current_path, colors::TEXT_COLOR);
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);
// Path bar separator // Path bar separator
sep_y = pathbar_y + FM_PATHBAR_H - 1; c.hline(0, pathbar_y + FM_PATHBAR_H - 1, c.w, colors::BORDER);
if (sep_y < ch) {
for (int x = 0; x < cw; x++)
pixels[sep_y * cw + x] = sep_px;
}
if (fm->grid_view) { if (fm->grid_view) {
// ---- Grid View ---- // ---- Grid View ----
int list_y = FM_TOOLBAR_H + FM_PATHBAR_H; int list_y = FM_TOOLBAR_H + FM_PATHBAR_H;
int list_h = ch - list_y; int list_h = c.h - list_y;
int cols = (cw - FM_SCROLLBAR_W) / FM_GRID_CELL_W; int cols = (c.w - FM_SCROLLBAR_W) / FM_GRID_CELL_W;
if (cols < 1) cols = 1; if (cols < 1) cols = 1;
int rows = (fm->entry_count + cols - 1) / cols; int rows = (fm->entry_count + cols - 1) / cols;
int content_h = rows * FM_GRID_CELL_H; int content_h = rows * FM_GRID_CELL_H;
// Update scrollbar // 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.content_height = content_h;
fm->scrollbar.view_height = list_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; int cell_y = list_y + row * FM_GRID_CELL_H - fm->scrollbar.scroll_offset;
// Skip if entirely off-screen // 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 // Selection highlight
if (i == fm->selected) { if (i == fm->selected) {
uint32_t sel_px = colors::MENU_HOVER.to_pixel(); int sy = gui_max(cell_y, list_y);
for (int y = gui_max(cell_y, list_y); y < gui_min(cell_y + FM_GRID_CELL_H, ch); y++) int sh = gui_min(cell_y + FM_GRID_CELL_H, c.h) - sy;
for (int x = cell_x; x < gui_min(cell_x + FM_GRID_CELL_W, cw - FM_SCROLLBAR_W); x++) int sw = gui_min(FM_GRID_CELL_W, c.w - FM_SCROLLBAR_W - cell_x);
pixels[y * cw + x] = sel_px; if (sh > 0 && sw > 0)
c.fill_rect(cell_x, sy, sw, sh, colors::MENU_HOVER);
} }
// Large icon centered horizontally // Large icon centered horizontally
int icon_x = cell_x + (FM_GRID_CELL_W - FM_GRID_ICON) / 2; int icon_x = cell_x + (FM_GRID_CELL_W - FM_GRID_ICON) / 2;
int icon_y = cell_y + FM_GRID_PAD; int icon_y = cell_y + FM_GRID_PAD;
if (ds && fm->entry_types[i] == 1 && ds->icon_folder_lg.pixels) { 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) { } 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) { } 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 { } else {
uint32_t icon_px = fm->is_dir[i] Color icon_c = fm->is_dir[i]
? Color::from_rgb(0xFF, 0xBD, 0x2E).to_pixel() ? Color::from_rgb(0xFF, 0xBD, 0x2E)
: Color::from_rgb(0x90, 0x90, 0x90).to_pixel(); : Color::from_rgb(0x90, 0x90, 0x90);
for (int dy = 0; dy < FM_GRID_ICON && icon_y + dy < ch && icon_y + dy >= list_y; dy++) int iy_clip = gui_max(icon_y, list_y);
for (int dx = 0; dx < FM_GRID_ICON && icon_x + dx < cw; dx++) int ih_clip = FM_GRID_ICON - (iy_clip - icon_y);
pixels[(icon_y + dy) * cw + (icon_x + dx)] = icon_px; 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 // 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; int tx = cell_x + (FM_GRID_CELL_W - tw) / 2;
if (tx < cell_x) tx = cell_x; if (tx < cell_x) tx = cell_x;
int ty = icon_y + FM_GRID_ICON + 2; int ty = icon_y + FM_GRID_ICON + 2;
if (ty >= list_y && ty + FONT_HEIGHT <= ch) if (ty >= list_y && ty + FONT_HEIGHT <= c.h)
draw_text_to_pixels(pixels, cw, ch, tx, ty, label, text_px); c.text(tx, ty, label, colors::TEXT_COLOR);
} }
} else { } else {
// ---- List View ---- // ---- List View ----
// ---- Column headers ---- // ---- Column headers ----
int header_y = FM_TOOLBAR_H + FM_PATHBAR_H; int header_y = FM_TOOLBAR_H + FM_PATHBAR_H;
uint32_t header_px = Color::from_rgb(0xF8, 0xF8, 0xF8).to_pixel(); c.fill_rect(0, header_y, c.w, FM_HEADER_H, Color::from_rgb(0xF8, 0xF8, 0xF8));
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;
uint32_t dim_px = Color::from_rgb(0x88, 0x88, 0x88).to_pixel();
int name_col_x = 8; int name_col_x = 8;
int size_col_x = cw - FM_SCROLLBAR_W - 120; int size_col_x = c.w - FM_SCROLLBAR_W - 120;
int type_col_x = cw - FM_SCROLLBAR_W - 60; 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) 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) 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 // Header separator
sep_y = header_y + FM_HEADER_H - 1; c.hline(0, header_y + FM_HEADER_H - 1, c.w, colors::BORDER);
if (sep_y < ch) {
for (int x = 0; x < cw; x++)
pixels[sep_y * cw + x] = sep_px;
}
// Column separator lines // Column separator lines
if (size_col_x > 100) { if (size_col_x > 100) {
for (int y = header_y; y < ch; y++) c.vline(size_col_x - 4, header_y, c.h - header_y, colors::BORDER);
if (size_col_x - 4 >= 0 && size_col_x - 4 < cw)
pixels[y * cw + (size_col_x - 4)] = sep_px;
} }
// ---- File entries ---- // ---- File entries ----
int list_y = header_y + FM_HEADER_H; 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 visible_items = list_h / FM_ITEM_H;
int content_h = fm->entry_count * FM_ITEM_H; int content_h = fm->entry_count * FM_ITEM_H;
// Update scrollbar // 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.content_height = content_h;
fm->scrollbar.view_height = list_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++) { 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); 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 // Highlight selected
if (i == fm->selected) { if (i == fm->selected) {
uint32_t sel_px = colors::MENU_HOVER.to_pixel(); int sy = gui_max(iy, list_y);
for (int y = gui_max(iy, list_y); y < gui_min(iy + FM_ITEM_H, ch); y++) int sh = gui_min(iy + FM_ITEM_H, c.h) - sy;
for (int x = 0; x < cw - FM_SCROLLBAR_W; x++) if (sh > 0)
pixels[y * cw + x] = sel_px; c.fill_rect(0, sy, c.w - FM_SCROLLBAR_W, sh, colors::MENU_HOVER);
} }
// Icon // Icon
int icon_x = 8; int ico_x = 8;
int icon_y = iy + (FM_ITEM_H - 16) / 2; int ico_y = iy + (FM_ITEM_H - 16) / 2;
if (ds && fm->entry_types[i] == 1 && ds->icon_folder.pixels) { 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) { } 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) { } 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 { } else {
uint32_t icon_px = fm->is_dir[i] Color icon_c = fm->is_dir[i]
? Color::from_rgb(0xFF, 0xBD, 0x2E).to_pixel() ? Color::from_rgb(0xFF, 0xBD, 0x2E)
: Color::from_rgb(0x90, 0x90, 0x90).to_pixel(); : Color::from_rgb(0x90, 0x90, 0x90);
for (int dy = 0; dy < 16 && icon_y + dy < ch && icon_y + dy >= list_y; dy++) int iy_clip = gui_max(ico_y, list_y);
for (int dx = 0; dx < 16 && icon_x + dx < cw; dx++) int ih_clip = 16 - (iy_clip - ico_y);
pixels[(icon_y + dy) * cw + (icon_x + dx)] = icon_px; if (ih_clip > 0)
c.fill_rect(ico_x, iy_clip, 16, ih_clip, icon_c);
} }
// Name // Name
int tx = 30; int tx = 30;
int ty = iy + (FM_ITEM_H - FONT_HEIGHT) / 2; int ty = iy + (FM_ITEM_H - FONT_HEIGHT) / 2;
if (ty >= list_y && ty + FONT_HEIGHT <= ch) if (ty >= list_y && ty + FONT_HEIGHT <= c.h)
draw_text_to_pixels(pixels, cw, ch, tx, ty, fm->entry_names[i], text_px); c.text(tx, ty, fm->entry_names[i], colors::TEXT_COLOR);
// Size // 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]; char size_str[16];
format_size(size_str, fm->entry_sizes[i]); 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 // 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"; const char* type_str = "File";
if (fm->entry_types[i] == 1) type_str = "Dir"; if (fm->entry_types[i] == 1) type_str = "Dir";
else if (fm->entry_types[i] == 2) type_str = "Exec"; 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 ---- // ---- Scrollbar ----
// Draw scrollbar directly to pixels
if (fm->scrollbar.content_height > fm->scrollbar.view_height) { if (fm->scrollbar.content_height > fm->scrollbar.view_height) {
uint32_t sb_bg = colors::SCROLLBAR_BG.to_pixel(); Color sb_fg_color = (fm->scrollbar.hovered || fm->scrollbar.dragging)
uint32_t sb_fg = (fm->scrollbar.hovered || fm->scrollbar.dragging) ? fm->scrollbar.hover_fg : fm->scrollbar.fg;
? fm->scrollbar.hover_fg.to_pixel() : fm->scrollbar.fg.to_pixel();
int sbx = fm->scrollbar.bounds.x; int sbx = fm->scrollbar.bounds.x;
int sby = fm->scrollbar.bounds.y; int sby = fm->scrollbar.bounds.y;
int sbw = fm->scrollbar.bounds.w; int sbw = fm->scrollbar.bounds.w;
int sbh = fm->scrollbar.bounds.h; int sbh = fm->scrollbar.bounds.h;
for (int y = sby; y < sby + sbh && y < ch; y++) c.fill_rect(sbx, sby, sbw, sbh, colors::SCROLLBAR_BG);
for (int x = sbx; x < sbx + sbw && x < cw; x++)
pixels[y * cw + x] = sb_bg;
int th = fm->scrollbar.thumb_height(); int th = fm->scrollbar.thumb_height();
int tty = fm->scrollbar.thumb_y(); int tty = fm->scrollbar.thumb_y();
for (int y = tty; y < tty + th && y < sby + sbh && y < ch; y++) c.fill_rect(sbx + 1, tty, sbw - 2, th, sb_fg_color);
for (int x = sbx + 1; x < sbx + sbw - 1 && x < cw; x++)
pixels[y * cw + x] = sb_fg;
} }
} }
+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; SysInfoState* si = (SysInfoState*)win->app_data;
if (!si) return; if (!si) return;
// Refresh uptime
si->uptime_ms = zenith::get_milliseconds(); si->uptime_ms = zenith::get_milliseconds();
Rect cr = win->content_rect(); Canvas c(win);
int cw = cr.w; c.fill(colors::WINDOW_BG);
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;
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
uint32_t accent_px = colors::ACCENT.to_pixel();
int y = 16; int y = 16;
int x = 16; int x = 16;
char line[128]; char line[128];
// Title // 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; y += FONT_HEIGHT + 12;
// Separator // Separator
uint32_t sep_px = colors::BORDER.to_pixel(); c.hline(x, y, c.w - 2 * x, colors::BORDER);
for (int sx = x; sx < cw - x && y < ch; sx++)
pixels[y * cw + sx] = sep_px;
y += 8; y += 8;
// OS Name // OS Name
snprintf(line, sizeof(line), "OS: %s", si->sys_info.osName); snprintf(line, sizeof(line), "OS: %s", si->sys_info.osName);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// OS Version // OS Version
snprintf(line, sizeof(line), "Version: %s", si->sys_info.osVersion); snprintf(line, sizeof(line), "Version: %s", si->sys_info.osVersion);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// API Version // API Version
snprintf(line, sizeof(line), "API: %d", (int)si->sys_info.apiVersion); snprintf(line, sizeof(line), "API: %d", (int)si->sys_info.apiVersion);
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// Max Processes // Max Processes
snprintf(line, sizeof(line), "Max PIDs: %d", (int)si->sys_info.maxProcesses); 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; y += FONT_HEIGHT + 12;
// Uptime // Uptime
@@ -73,15 +59,14 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
int up_min = up_sec / 60; int up_min = up_sec / 60;
int up_hr = up_min / 60; int up_hr = up_min / 60;
snprintf(line, sizeof(line), "Uptime: %d:%02d:%02d", up_hr, up_min % 60, up_sec % 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; y += FONT_HEIGHT + 12;
// Network section // 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; y += FONT_HEIGHT + 8;
for (int sx = x; sx < cw - x && y < ch; sx++) c.hline(x, y, c.w - 2 * x, colors::BORDER);
pixels[y * cw + sx] = sep_px;
y += 8; y += 8;
// IP Address // IP Address
@@ -89,39 +74,35 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
snprintf(line, sizeof(line), "IP: %d.%d.%d.%d", snprintf(line, sizeof(line), "IP: %d.%d.%d.%d",
(int)(ip & 0xFF), (int)((ip >> 8) & 0xFF), (int)(ip & 0xFF), (int)((ip >> 8) & 0xFF),
(int)((ip >> 16) & 0xFF), (int)((ip >> 24) & 0xFF)); (int)((ip >> 16) & 0xFF), (int)((ip >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// Subnet // Subnet
uint32_t mask = si->net_cfg.subnetMask; uint32_t mask = si->net_cfg.subnetMask;
snprintf(line, sizeof(line), "Subnet: %d.%d.%d.%d", snprintf(line, sizeof(line), "Subnet: %d.%d.%d.%d",
(int)(mask & 0xFF), (int)((mask >> 8) & 0xFF), (int)(mask & 0xFF), (int)((mask >> 8) & 0xFF),
(int)((mask >> 16) & 0xFF), (int)((mask >> 24) & 0xFF)); (int)((mask >> 16) & 0xFF), (int)((mask >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// Gateway // Gateway
uint32_t gw = si->net_cfg.gateway; uint32_t gw = si->net_cfg.gateway;
snprintf(line, sizeof(line), "Gateway: %d.%d.%d.%d", snprintf(line, sizeof(line), "Gateway: %d.%d.%d.%d",
(int)(gw & 0xFF), (int)((gw >> 8) & 0xFF), (int)(gw & 0xFF), (int)((gw >> 8) & 0xFF),
(int)((gw >> 16) & 0xFF), (int)((gw >> 24) & 0xFF)); (int)((gw >> 16) & 0xFF), (int)((gw >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// DNS // DNS
uint32_t dns = si->net_cfg.dnsServer; uint32_t dns = si->net_cfg.dnsServer;
snprintf(line, sizeof(line), "DNS: %d.%d.%d.%d", snprintf(line, sizeof(line), "DNS: %d.%d.%d.%d",
(int)(dns & 0xFF), (int)((dns >> 8) & 0xFF), (int)(dns & 0xFF), (int)((dns >> 8) & 0xFF),
(int)((dns >> 16) & 0xFF), (int)((dns >> 24) & 0xFF)); (int)((dns >> 16) & 0xFF), (int)((dns >> 24) & 0xFF));
draw_text_to_pixels(pixels, cw, ch, x, y, line, text_px); c.kv_line(x, &y, line, colors::TEXT_COLOR);
y += FONT_HEIGHT + 6;
// MAC Address // MAC Address
snprintf(line, sizeof(line), "MAC: %02x:%02x:%02x:%02x:%02x:%02x", 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[0], (unsigned)si->net_cfg.macAddress[1],
(unsigned)si->net_cfg.macAddress[2], (unsigned)si->net_cfg.macAddress[3], (unsigned)si->net_cfg.macAddress[2], (unsigned)si->net_cfg.macAddress[3],
(unsigned)si->net_cfg.macAddress[4], (unsigned)si->net_cfg.macAddress[5]); (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) { 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; TextEditorState* te = (TextEditorState*)win->app_data;
if (!te) return; if (!te) return;
Rect cr = win->content_rect(); Canvas c(win);
int cw = cr.w; c.fill(colors::WINDOW_BG);
int ch = cr.h;
uint32_t* pixels = win->content;
// Background int text_area_h = c.h - TE_STATUS_H;
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 visible_lines = text_area_h / FONT_HEIGHT; int visible_lines = text_area_h / FONT_HEIGHT;
te_ensure_cursor_visible(te, visible_lines); te_ensure_cursor_visible(te, visible_lines);
// Line number gutter background // Line number gutter background
uint32_t gutter_px = Color::from_rgb(0xF0, 0xF0, 0xF0).to_pixel(); c.fill_rect(0, 0, TE_LINE_NUM_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0));
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;
// Gutter separator // Gutter separator
uint32_t sep_px = colors::BORDER.to_pixel(); c.vline(TE_LINE_NUM_W, 0, text_area_h, colors::BORDER);
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;
}
// Draw lines // 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 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; 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 // Cursor line highlighting
if (line == te->cursor_line) { if (line == te->cursor_line) {
for (int y = py; y < py + FONT_HEIGHT && y < text_area_h; y++) int hl_h = gui_min(FONT_HEIGHT, text_area_h - py);
for (int x = TE_LINE_NUM_W + 1; x < cw; x++) if (hl_h > 0)
pixels[y * cw + x] = cursor_line_px; c.fill_rect(TE_LINE_NUM_W + 1, py, c.w - TE_LINE_NUM_W - 1, hl_h, cursor_line_color);
} }
// Line number // Line number
char num_str[8]; char num_str[8];
snprintf(num_str, 8, "%4d", line + 1); 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_start = te->line_offsets[line];
int line_len = te_line_length(te, line); int line_len = te_line_length(te, line);
for (int ci = 0; ci < line_len; ci++) { for (int ci = 0; ci < line_len; ci++) {
int px = text_start_x + ci * FONT_WIDTH - te->scroll_x; int px = text_start_x + ci * FONT_WIDTH - te->scroll_x;
if (px + FONT_WIDTH <= TE_LINE_NUM_W + 1) continue; 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]; char ch = te->buffer[line_start + ci];
if (c >= 32 || c < 0) { if (ch >= 32 || ch < 0) {
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT]; const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
for (int fy = 0; fy < FONT_HEIGHT && py + fy < text_area_h; fy++) { for (int fy = 0; fy < FONT_HEIGHT && py + fy < text_area_h; fy++) {
uint8_t bits = glyph[fy]; uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) { for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) { if (bits & (0x80 >> fx)) {
int dx = px + fx; int dx = px + fx;
int dy = py + fy; int dy = py + fy;
if (dx > TE_LINE_NUM_W && dx < cw && dy >= 0 && dy < text_area_h) if (dx > TE_LINE_NUM_W && dx < c.w && dy >= 0 && dy < text_area_h)
pixels[dy * cw + dx] = text_px; c.pixels[dy * c.w + dx] = text_px;
} }
} }
} }
@@ -370,24 +356,17 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
// Draw cursor // Draw cursor
if (line == te->cursor_line) { if (line == te->cursor_line) {
int cx = text_start_x + te->cursor_col * FONT_WIDTH - te->scroll_x; int cx = text_start_x + te->cursor_col * FONT_WIDTH - te->scroll_x;
if (cx > TE_LINE_NUM_W && cx + 2 <= cw) { if (cx > TE_LINE_NUM_W && cx + 2 <= c.w) {
for (int y = py; y < py + FONT_HEIGHT && y < text_area_h; y++) { int cur_h = gui_min(FONT_HEIGHT, text_area_h - py);
pixels[y * cw + cx] = cursor_px; if (cur_h > 0)
if (cx + 1 < cw) c.fill_rect(cx, py, 2, cur_h, colors::ACCENT);
pixels[y * cw + cx + 1] = cursor_px;
}
} }
} }
} }
// ---- Status bar ---- // ---- Status bar ----
int status_y = ch - TE_STATUS_H; int status_y = c.h - TE_STATUS_H;
uint32_t status_bg = Color::from_rgb(0x2B, 0x3E, 0x50).to_pixel(); c.fill_rect(0, status_y, c.w, TE_STATUS_H, Color::from_rgb(0x2B, 0x3E, 0x50));
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;
// Filename + modified flag // Filename + modified flag
char status_left[128]; char status_left[128];
@@ -396,16 +375,13 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
} else { } else {
snprintf(status_left, 128, " Untitled%s", te->modified ? " [modified]" : ""); 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, c.text(4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_left, colors::PANEL_TEXT);
status_left, status_fg);
// Cursor position (right side) // Cursor position (right side)
char status_right[32]; char status_right[32];
snprintf(status_right, 32, "Ln %d, Col %d ", te->cursor_line + 1, te->cursor_col + 1); 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; int sr_w = zenith::slen(status_right) * FONT_WIDTH;
draw_text_to_pixels(pixels, cw, ch, cw - sr_w - 4, c.text(c.w - sr_w - 4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_right, colors::PANEL_TEXT);
status_y + (TE_STATUS_H - FONT_HEIGHT) / 2,
status_right, status_fg);
} }
// ============================================================================ // ============================================================================
+32 -78
View File
@@ -22,7 +22,7 @@ static constexpr int WIKI_SCROLLBAR_W = 12;
struct WikiDisplayLine { struct WikiDisplayLine {
char text[256]; char text[256];
uint32_t color; Color color;
}; };
struct WikiState { 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) // 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; if (ws->lineCount >= ws->lineCap) return;
WikiDisplayLine* dl = &ws->lines[ws->lineCount]; WikiDisplayLine* dl = &ws->lines[ws->lineCount];
int copyLen = len; 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, 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; if (textLen <= 0 || maxChars <= 0) return;
const char* p = text; const char* p = text;
const char* end = text + textLen; 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; int maxChars = (contentW - 24 - WIKI_SCROLLBAR_W) / FONT_WIDTH;
if (maxChars < 20) maxChars = 20; if (maxChars < 20) maxChars = 20;
uint32_t accent_px = colors::ACCENT.to_pixel(); Color accent_c = colors::ACCENT;
uint32_t green_px = Color::from_rgb(0x2E, 0x7D, 0x32).to_pixel(); Color green_c = Color::from_rgb(0x2E, 0x7D, 0x32);
uint32_t text_px = colors::TEXT_COLOR.to_pixel(); Color text_c = colors::TEXT_COLOR;
// Title // Title
if (title && title[0]) { 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 // Blank separator
if (ws->lineCount > 0) if (ws->lineCount > 0)
wiki_add_line(ws, "", 0, text_px); wiki_add_line(ws, "", 0, text_c);
// Process extract line by line // Process extract line by line
const char* p = extract; const char* p = extract;
@@ -195,7 +195,7 @@ static void wiki_build_display(WikiState* ws, const char* title,
if (p < end) p++; if (p < end) p++;
if (lineLen == 0) { if (lineLen == 0) {
wiki_add_line(ws, "", 0, text_px); wiki_add_line(ws, "", 0, text_c);
continue; 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--;
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) { if (ei > si) {
char secBuf[256]; char secBuf[256];
int secLen = ei - si; int secLen = ei - si;
if (secLen > 255) secLen = 255; if (secLen > 255) secLen = 255;
for (int i = 0; i < secLen; i++) secBuf[i] = lineStart[si + i]; for (int i = 0; i < secLen; i++) secBuf[i] = lineStart[si + i];
secBuf[secLen] = '\0'; secBuf[secLen] = '\0';
wiki_add_line(ws, secBuf, secLen, green_px); wiki_add_line(ws, secBuf, secLen, green_c);
} }
continue; continue;
} }
// Regular text // 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; WikiState* ws = (WikiState*)win->app_data;
if (!ws) return; if (!ws) return;
Rect cr = win->content_rect(); Canvas c(win);
int cw = cr.w; c.fill(colors::WINDOW_BG);
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;
// ---- Toolbar background ---- // ---- Toolbar background ----
uint32_t toolbar_bg = Color::from_rgb(0xF5, 0xF5, 0xF5).to_pixel(); c.fill_rect(0, 0, c.w, WIKI_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
for (int y = 0; y < WIKI_TOOLBAR_H && y < ch; y++)
for (int x = 0; x < cw; x++)
pixels[y * cw + x] = toolbar_bg;
// Toolbar separator line // Toolbar separator line
uint32_t sep_px = colors::BORDER.to_pixel(); c.hline(0, WIKI_TOOLBAR_H, c.w, colors::BORDER);
if (WIKI_TOOLBAR_H < ch)
for (int x = 0; x < cw; x++)
pixels[WIKI_TOOLBAR_H * cw + x] = sep_px;
// ---- Draw search box outline ---- // ---- Draw search box outline ----
int tb_x = 8; int tb_x = 8;
int tb_y = 6; int tb_y = 6;
int tb_w = cw - 90; int tb_w = c.w - 90;
int tb_h = 24; int tb_h = 24;
if (tb_w < 100) tb_w = 100; if (tb_w < 100) tb_w = 100;
// TextBox background // TextBox background + border
uint32_t white_px = colors::WHITE.to_pixel(); c.fill_rect(tb_x, tb_y, tb_w, tb_h, colors::WHITE);
uint32_t border_px = colors::BORDER.to_pixel(); c.rect(tb_x, tb_y, tb_w, tb_h, colors::BORDER);
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;
}
// Search text // Search text
uint32_t text_px = colors::TEXT_COLOR.to_pixel(); c.text(tb_x + 4, tb_y + (tb_h - FONT_HEIGHT) / 2, ws->searchQuery, colors::TEXT_COLOR);
draw_text_to_pixels(pixels, cw, ch, tb_x + 4, tb_y + (tb_h - FONT_HEIGHT) / 2,
ws->searchQuery, text_px);
// ---- Search button ---- // ---- Search button ----
int btn_x = tb_x + tb_w + 6; int btn_x = tb_x + tb_w + 6;
int btn_y = tb_y; int btn_y = tb_y;
int btn_w = 66; int btn_w = 66;
int btn_h = tb_h; int btn_h = tb_h;
uint32_t accent_px = colors::ACCENT.to_pixel(); c.button(btn_x, btn_y, btn_w, btn_h, "Search", colors::ACCENT, colors::WHITE, 0);
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);
// ---- Content area ---- // ---- Content area ----
int content_y = WIKI_TOOLBAR_H + 1; 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); int visibleLines = content_h / (FONT_HEIGHT + 4);
if (visibleLines < 1) visibleLines = 1; if (visibleLines < 1) visibleLines = 1;
if (ws->mode == WikiState::FETCHING) { if (ws->mode == WikiState::FETCHING) {
draw_text_to_pixels(pixels, cw, ch, 16, content_y + 16, c.text(16, content_y + 16, "Loading...", Color::from_rgb(0x88, 0x88, 0x88));
"Loading...", Color::from_rgb(0x88, 0x88, 0x88).to_pixel());
} else if (ws->mode == WikiState::WIKI_ERROR) { } else if (ws->mode == WikiState::WIKI_ERROR) {
draw_text_to_pixels(pixels, cw, ch, 16, content_y + 16, c.text(16, content_y + 16, ws->statusMsg, colors::CLOSE_BTN);
ws->statusMsg, colors::CLOSE_BTN.to_pixel());
} else if (ws->mode == WikiState::IDLE) { } else if (ws->mode == WikiState::IDLE) {
draw_text_to_pixels(pixels, cw, ch, 16, content_y + 16, c.text(16, content_y + 16,
"Type a topic and press Enter or click Search.", "Type a topic and press Enter or click Search.",
Color::from_rgb(0x88, 0x88, 0x88).to_pixel()); Color::from_rgb(0x88, 0x88, 0x88));
} else if (ws->mode == WikiState::DONE && ws->lineCount > 0) { } else if (ws->mode == WikiState::DONE && ws->lineCount > 0) {
int y = content_y + 8; int y = content_y + 8;
int lineH = FONT_HEIGHT + 4; 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]; WikiDisplayLine* dl = &ws->lines[i];
if (dl->text[0] != '\0') { 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; y += lineH;
} }
// ---- Scrollbar ---- // ---- Scrollbar ----
if (ws->lineCount > visibleLines) { 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_y = content_y;
int sb_h = content_h; int sb_h = content_h;
// Scrollbar track c.fill_rect(sb_x, sb_y, WIKI_SCROLLBAR_W, sb_h, colors::SCROLLBAR_BG);
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;
// Thumb // Thumb
int maxScroll = ws->lineCount - visibleLines; int maxScroll = ws->lineCount - visibleLines;
@@ -393,10 +350,7 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) {
if (thumbH < 20) thumbH = 20; if (thumbH < 20) thumbH = 20;
int thumbY = sb_y + (ws->scrollY * (sb_h - thumbH)) / maxScroll; int thumbY = sb_y + (ws->scrollY * (sb_h - thumbH)) / maxScroll;
uint32_t sb_fg = colors::SCROLLBAR_FG.to_pixel(); c.fill_rect(sb_x + 2, thumbY, WIKI_SCROLLBAR_W - 4, thumbH, colors::SCROLLBAR_FG);
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;
} }
} }
} }
+3 -83
View File
@@ -16,6 +16,7 @@
#include <gui/svg.hpp> #include <gui/svg.hpp>
#include <gui/widgets.hpp> #include <gui/widgets.hpp>
#include <gui/window.hpp> #include <gui/window.hpp>
#include <gui/canvas.hpp>
#include <gui/terminal.hpp> #include <gui/terminal.hpp>
#include <gui/desktop.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]); (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 // 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 // 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_texteditor_with_file(DesktopState* ds, const char* path);
void open_klog(DesktopState* ds); void open_klog(DesktopState* ds);
void open_wiki(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_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_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; ds->net_popup_open = false;
zenith::get_netcfg(&ds->cached_net_cfg); zenith::get_netcfg(&ds->cached_net_cfg);
ds->net_cfg_last_poll = zenith::get_milliseconds(); 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); draw_text(fb, title_x, title_y, win->title, colors::TEXT_COLOR);
// Call app draw callback to render content // Call app draw callback to render content (skip during resize — buffer is old size)
if (win->on_draw) { if (win->on_draw && !win->resizing) {
win->on_draw(win, fb); 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(); Rect cr = win->content_rect();
if (win->content) { 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) // 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_W = 220;
static constexpr int MENU_ITEM_H = 40; static constexpr int MENU_ITEM_H = 40;
@@ -371,6 +380,8 @@ static void desktop_draw_app_menu(DesktopState* ds) {
{ "Text Editor", &ds->icon_texteditor }, { "Text Editor", &ds->icon_texteditor },
{ "Kernel Log", &ds->icon_terminal }, { "Kernel Log", &ds->icon_terminal },
{ "Wikipedia", &ds->icon_wikipedia }, { "Wikipedia", &ds->icon_wikipedia },
{ "About", &ds->icon_settings },
{ "Reboot", &ds->icon_reboot },
}; };
int mx = ds->mouse.x; 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++) { for (int i = 0; i < MENU_ITEM_COUNT; i++) {
int iy = menu_y + 4 + i * MENU_ITEM_H; int iy = menu_y + 4 + i * MENU_ITEM_H;
// Thin separator line after System Info (before utility apps) // Thin separator lines before utility apps and before Settings
if (i == 3) { if (i == 3 || i == 7) {
int sep_y = iy - 1; int sep_y = iy - 1;
for (int sx = menu_x + 8; sx < menu_x + MENU_W - 8; sx++) for (int sx = menu_x + 8; sx < menu_x + MENU_W - 8; sx++)
fb.put_pixel(sx, sep_y, colors::BORDER); 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); 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) { void gui::desktop_compose(DesktopState* ds) {
Framebuffer& fb = ds->fb; Framebuffer& fb = ds->fb;
@@ -508,8 +677,91 @@ void gui::desktop_compose(DesktopState* ds) {
desktop_draw_net_popup(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 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) { 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_pressed = (buttons & 0x01) && !(prev & 0x01);
bool left_held = (buttons & 0x01); bool left_held = (buttons & 0x01);
bool left_released = !(buttons & 0x01) && (prev & 0x01); bool left_released = !(buttons & 0x01) && (prev & 0x01);
bool right_pressed = (buttons & 0x02) && !(prev & 0x02);
MouseEvent ev; MouseEvent ev;
ev.x = mx; ev.x = mx;
@@ -528,6 +781,42 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
ev.prev_buttons = prev; ev.prev_buttons = prev;
ev.scroll = ds->mouse.scrollDelta; 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 // Check for ongoing window drags first
for (int i = 0; i < ds->window_count; i++) { for (int i = 0; i < ds->window_count; i++) {
Window* win = &ds->windows[i]; Window* win = &ds->windows[i];
@@ -542,6 +831,88 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
} }
if (left_released) { if (left_released) {
win->dragging = false; 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; return;
} }
@@ -566,6 +937,8 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
case 4: open_texteditor(ds); break; case 4: open_texteditor(ds); break;
case 5: open_klog(ds); break; case 5: open_klog(ds); break;
case 6: open_wiki(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; ds->app_menu_open = false;
} }
@@ -597,6 +970,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
if (mx < 36) { if (mx < 36) {
ds->app_menu_open = !ds->app_menu_open; ds->app_menu_open = !ds->app_menu_open;
ds->net_popup_open = false; ds->net_popup_open = false;
ds->ctx_menu_open = false;
return; 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)) { if (ds->net_icon_rect.w > 0 && ds->net_icon_rect.contains(mx, my)) {
ds->net_popup_open = !ds->net_popup_open; ds->net_popup_open = !ds->net_popup_open;
ds->app_menu_open = false; ds->app_menu_open = false;
ds->ctx_menu_open = false;
return; return;
} }
// Window indicator buttons // Window indicator buttons
@@ -683,6 +1058,26 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
return; 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) // Check titlebar (start drag)
Rect tb = win->titlebar_rect(); Rect tb = win->titlebar_rect();
if (tb.contains(mx, my)) { if (tb.contains(mx, my)) {
@@ -718,6 +1113,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
} }
ds->app_menu_open = false; ds->app_menu_open = false;
ds->ctx_menu_open = false;
} }
// Handle scroll events on focused window // Handle scroll events on focused window
@@ -728,6 +1124,26 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
win->on_mouse(win, ev); 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) { 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.
BIN
View File
Binary file not shown.
+2
View File
@@ -50,6 +50,8 @@ ICONS=(
"devices/scalable/network-wired.svg" "devices/scalable/network-wired.svg"
"mimetypes/scalable/text-x-generic.svg" "mimetypes/scalable/text-x-generic.svg"
"mimetypes/scalable/application-x-executable.svg" "mimetypes/scalable/application-x-executable.svg"
"categories/scalable/help-about.svg"
"categories/scalable/system-reboot.svg"
) )
copied=0 copied=0