feat: Desktop improvements
This commit is contained in:
@@ -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
|
||||
@@ -51,6 +51,12 @@ struct DesktopState {
|
||||
SvgIcon icon_file_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;
|
||||
Zenith::NetCfg cached_net_cfg;
|
||||
uint64_t net_cfg_last_poll;
|
||||
|
||||
@@ -217,20 +217,175 @@ static constexpr uint16_t cursor_fill[16] = {
|
||||
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)
|
||||
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 white = colors::WHITE;
|
||||
|
||||
for (int row = 0; row < 16; row++) {
|
||||
uint16_t outline = cursor_outline[row];
|
||||
uint16_t fill = cursor_fill[row];
|
||||
uint16_t outline = outline_data[row];
|
||||
uint16_t fill = fill_data[row];
|
||||
for (int col = 0; col < 16; col++) {
|
||||
uint16_t mask = (uint16_t)(0x8000 >> col);
|
||||
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) {
|
||||
fb.put_pixel(x + col, y + row, white);
|
||||
fb.put_pixel(x + ox + col, y + oy + row, white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,20 @@ namespace gui {
|
||||
|
||||
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 BORDER_WIDTH = 1;
|
||||
static constexpr int SHADOW_SIZE = 3;
|
||||
static constexpr int BTN_RADIUS = 6;
|
||||
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;
|
||||
using WindowDrawCallback = void (*)(Window* win, Framebuffer& fb);
|
||||
@@ -42,6 +51,9 @@ struct Window {
|
||||
int drag_offset_x, drag_offset_y;
|
||||
|
||||
bool resizing;
|
||||
ResizeEdge resize_edge;
|
||||
Rect resize_start_frame;
|
||||
int resize_start_mx, resize_start_my;
|
||||
|
||||
Rect saved_frame;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user