Files
SUB-OS/gui/gui_gfx.c
T
subhobhai943andClaude Opus 5 1aee7c7a0d feat(gui): Add SUB-WT widget toolkit, icon set, modal dialogs, six apps and a reworked window manager
Turns the desktop from a fixed set of hand-drawn panels into a real
environment applications can be written against.

SUB-WT immediate-mode toolkit (gui/gui_widgets.c):
  Buttons, labels, checkboxes, radios, sliders, list boxes, text fields, tab
  bars, scrollbars, progress bars, sparklines and badges. An app feeds the WM
  event into the toolkit and then calls widgets from its paint routine, so
  each call both draws the control and reports what the user did with it --
  no retained widget tree to keep in sync with window state.

Icons and dialogs:
  gui_icons.c holds 14 16x16 palette-indexed glyphs stored as hex text, so the
  art stays hand-editable and picks up theme changes for free; they scale by
  integer factors and can be drawn as tint stencils. gui_dialog.c adds modal
  info/warning/error/confirm dialogs that dim the desktop, take exclusive
  input, word-wrap on spaces and report results through a callback.

Applications (gui/gui_apps_ext.c):
  Settings (tabbed appearance/input/power), Task Manager (live process table
  over task_find_by_pid with memory and heap gauges), Kernel Log viewer (reads
  the dmesg ring, strips ANSI, colours by severity, scrolls), KTest Runner
  (drives the in-kernel suites and reports 21/21 with timing), Clock & Calendar
  (Zeller's congruence month grid) and a Text Editor.

Window manager (gui/gui_wm.c):
  Minimize/maximize/restore buttons, a corner resize grip, edge snapping, tile
  and cascade layouts, F5-F9 shortcuts, title truncation, and index-based
  enumeration so the taskbar no longer assumes window ids stay dense.

Desktop shell (gui/gui_desktop.c):
  Launcher icon grid, richer start menu, right-click context menu, taskbar
  buttons that minimize and restore, and a live CPU trace in the tray.

Two interaction bugs fixed along the way:
  - keyboard_get_key() blocks until a key arrives, so calling it every frame
    froze the compositor; it is now gated on keyboard_has_key(), and stale
    console input is drained before the first frame.
  - When the desktop consumed a click, the WM's press-edge detector kept the
    pre-press state and read the still-held button as a fresh press on the
    next frame, stealing focus from whatever the click had just opened.
    gui_wm_sync_button_state() keeps the two layers in step.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01L5yKJT4Wo2cV3rfPrwY5MP
2026-08-25 00:18:55 +05:30

340 lines
13 KiB
C

// High-Performance 2D Graphics Engine & Software Compositor for SUB-OS
#include <gui/gui_gfx.h>
#include <mm/kmalloc.h>
#include <lib/string.h>
static uint32_t* g_frontbuffer = NULL;
static uint32_t* g_backbuffer = NULL;
static int g_width = 800;
static int g_height = 600;
// Standard 8x8 font bitmap table for printable ASCII characters (32..126)
static const uint8_t font8x8[96][8] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // space
{0x18,0x3c,0x3c,0x18,0x18,0x00,0x18,0x00}, // !
{0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00}, // "
{0x6c,0x6c,0xfe,0x6c,0xfe,0x6c,0x6c,0x00}, // #
{0x18,0x3e,0x60,0x3c,0x06,0x7c,0x18,0x00}, // $
{0x00,0x63,0x66,0x0c,0x18,0x33,0x63,0x00}, // %
{0x38,0x6c,0x38,0x76,0xdc,0xcc,0x76,0x00}, // &
{0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00}, // '
{0x0c,0x18,0x30,0x30,0x30,0x18,0x0c,0x00}, // (
{0x30,0x18,0x0c,0x0c,0x0c,0x18,0x30,0x00}, // )
{0x00,0x66,0x3c,0xff,0x3c,0x66,0x00,0x00}, // *
{0x00,0x18,0x18,0x7e,0x18,0x18,0x00,0x00}, // +
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30}, // ,
{0x00,0x00,0x00,0x7e,0x00,0x00,0x00,0x00}, // -
{0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, // .
{0x06,0x0c,0x18,0x30,0x60,0xc0,0x80,0x00}, // /
{0x7c,0xc6,0xce,0xd6,0xe6,0xc6,0x7c,0x00}, // 0
{0x18,0x38,0x18,0x18,0x18,0x18,0x7e,0x00}, // 1
{0x7c,0xc6,0x06,0x1c,0x30,0x66,0xfe,0x00}, // 2
{0x7c,0xc6,0x06,0x3c,0x06,0xc6,0x7c,0x00}, // 3
{0x1c,0x3c,0x6c,0xcc,0xfe,0x0c,0x1e,0x00}, // 4
{0xfe,0xc0,0xfc,0x06,0x06,0xc6,0x7c,0x00}, // 5
{0x7c,0xc6,0xc0,0xfc,0xc6,0xc6,0x7c,0x00}, // 6
{0xfe,0x06,0x0c,0x18,0x30,0x30,0x30,0x00}, // 7
{0x7c,0xc6,0xc6,0x7c,0xc6,0xc6,0x7c,0x00}, // 8
{0x7c,0xc6,0xc6,0x7e,0x06,0xc6,0x7c,0x00}, // 9
{0x00,0x18,0x18,0x00,0x18,0x18,0x00,0x00}, // :
{0x00,0x18,0x18,0x00,0x18,0x18,0x30,0x00}, // ;
{0x06,0x0c,0x18,0x30,0x18,0x0c,0x06,0x00}, // <
{0x00,0x00,0x7e,0x00,0x7e,0x00,0x00,0x00}, // =
{0x60,0x30,0x18,0x0c,0x18,0x30,0x60,0x00}, // >
{0x7c,0xc6,0x0c,0x18,0x18,0x00,0x18,0x00}, // ?
{0x7c,0xc6,0xde,0xde,0xdc,0xc0,0x7c,0x00}, // @
{0x38,0x6c,0xc6,0xfe,0xc6,0xc6,0xc6,0x00}, // A
{0xfc,0x66,0x66,0x7c,0x66,0x66,0xfc,0x00}, // B
{0x3c,0x66,0xc0,0xc0,0xc0,0x66,0x3c,0x00}, // C
{0xf8,0x6c,0x66,0x66,0x66,0x6c,0xf8,0x00}, // D
{0xfe,0x62,0x68,0x78,0x68,0x62,0xfe,0x00}, // E
{0xfe,0x62,0x68,0x78,0x68,0x60,0xf0,0x00}, // F
{0x3c,0x66,0xc0,0xc0,0xce,0x66,0x3e,0x00}, // G
{0xc6,0xc6,0xc6,0xfe,0xc6,0xc6,0xc6,0x00}, // H
{0x3c,0x18,0x18,0x18,0x18,0x18,0x3c,0x00}, // I
{0x1e,0x0c,0x0c,0x0c,0xcc,0xcc,0x78,0x00}, // J
{0xe6,0x66,0x6c,0x78,0x6c,0x66,0xe6,0x00}, // K
{0xf0,0x60,0x60,0x60,0x62,0x66,0xfe,0x00}, // L
{0xc6,0xee,0xfe,0xd6,0xc6,0xc6,0xc6,0x00}, // M
{0xc6,0xe6,0xf6,0xde,0xce,0xc6,0xc6,0x00}, // N
{0x7c,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0x00}, // O
{0xfc,0x66,0x66,0x7c,0x60,0x60,0xf0,0x00}, // P
{0x7c,0xc6,0xc6,0xc6,0xd6,0xde,0x7c,0x06}, // Q
{0xfc,0x66,0x66,0x7c,0x6c,0x66,0xe6,0x00}, // R
{0x7c,0xc6,0x60,0x38,0x0c,0xc6,0x7c,0x00}, // S
{0x7e,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, // T
{0xc6,0xc6,0xc6,0xc6,0xc6,0xc6,0x7c,0x00}, // U
{0xc6,0xc6,0xc6,0xc6,0xc6,0x6c,0x38,0x00}, // V
{0xc6,0xc6,0xc6,0xd6,0xfe,0xee,0xc6,0x00}, // W
{0xc6,0xc6,0x6c,0x38,0x6c,0xc6,0xc6,0x00}, // X
{0x66,0x66,0x66,0x3c,0x18,0x18,0x18,0x00}, // Y
{0xfe,0x06,0x0c,0x18,0x30,0x60,0xfe,0x00}, // Z
{0x3c,0x30,0x30,0x30,0x30,0x30,0x3c,0x00}, // [
{0xc0,0x60,0x30,0x18,0x0c,0x06,0x02,0x00}, // backslash
{0x3c,0x0c,0x0c,0x0c,0x0c,0x0c,0x3c,0x00}, // ]
{0x10,0x38,0x6c,0xc6,0x00,0x00,0x00,0x00}, // ^
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff}, // _
{0x30,0x18,0x0c,0x00,0x00,0x00,0x00,0x00}, // `
{0x00,0x00,0x78,0x0c,0x7c,0xcc,0x76,0x00}, // a
{0xe0,0x60,0x7c,0x66,0x66,0x66,0xdc,0x00}, // b
{0x00,0x00,0x78,0xcc,0xc0,0xcc,0x78,0x00}, // c
{0x1c,0x0c,0x7c,0xcc,0xcc,0xcc,0x76,0x00}, // d
{0x00,0x00,0x78,0xcc,0xfc,0xc0,0x78,0x00}, // e
{0x38,0x6c,0x60,0xf0,0x60,0x60,0xf0,0x00}, // f
{0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0xf8}, // g
{0xe0,0x60,0x6c,0x76,0x66,0x66,0xe6,0x00}, // h
{0x18,0x00,0x38,0x18,0x18,0x18,0x3c,0x00}, // i
{0x0c,0x00,0x1c,0x0c,0x0c,0xcc,0xcc,0x78}, // j
{0xe0,0x60,0x66,0x6c,0x78,0x6c,0xe6,0x00}, // k
{0x38,0x18,0x18,0x18,0x18,0x18,0x3c,0x00}, // l
{0x00,0x00,0xec,0xfe,0xd6,0xc6,0xc6,0x00}, // m
{0x00,0x00,0xdc,0x66,0x66,0x66,0x66,0x00}, // n
{0x00,0x00,0x7c,0xc6,0xc6,0xc6,0x7c,0x00}, // o
{0x00,0x00,0xdc,0x66,0x66,0x7c,0x60,0xf0}, // p
{0x00,0x00,0x76,0xcc,0xcc,0x7c,0x0c,0x1e}, // q
{0x00,0x00,0xdc,0x76,0x60,0x60,0xf0,0x00}, // r
{0x00,0x00,0x7c,0xc0,0x78,0x0c,0xf8,0x00}, // s
{0x10,0x30,0x7c,0x30,0x30,0x34,0x18,0x00}, // t
{0x00,0x00,0xcc,0xcc,0xcc,0xcc,0x76,0x00}, // u
{0x00,0x00,0xc6,0xc6,0xc6,0x6c,0x38,0x00}, // v
{0x00,0x00,0xc6,0xc6,0xd6,0xfe,0x6c,0x00}, // w
{0x00,0x00,0xc6,0x6c,0x38,0x6c,0xc6,0x00}, // x
{0x00,0x00,0xcc,0xcc,0xcc,0x7c,0x0c,0xf8}, // y
{0x00,0x00,0xfc,0x18,0x30,0x60,0xfc,0x00}, // z
{0x0e,0x18,0x18,0x70,0x18,0x18,0x0e,0x00}, // {
{0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00}, // |
{0x70,0x18,0x18,0x0e,0x18,0x18,0x70,0x00}, // }
{0x76,0xdc,0x00,0x00,0x00,0x00,0x00,0x00}, // ~
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} // DEL
};
void gui_gfx_init(uint32_t* fb, int width, int height) {
g_frontbuffer = fb;
g_width = (width > 0) ? width : 800;
g_height = (height > 0) ? height : 600;
if (!g_backbuffer) {
g_backbuffer = (uint32_t*)kzalloc(g_width * g_height * sizeof(uint32_t));
}
}
int gui_gfx_get_width(void) { return g_width; }
int gui_gfx_get_height(void) { return g_height; }
uint32_t* gui_gfx_get_backbuffer(void) { return g_backbuffer; }
void gui_gfx_clear(uint32_t color) {
if (!g_backbuffer) return;
size_t count = g_width * g_height;
for (size_t i = 0; i < count; i++) {
g_backbuffer[i] = color;
}
}
void gui_gfx_draw_pixel(int x, int y, uint32_t color) {
if (!g_backbuffer || x < 0 || y < 0 || x >= g_width || y >= g_height) return;
g_backbuffer[y * g_width + x] = color;
}
void gui_gfx_draw_pixel_blend(int x, int y, uint32_t color, uint8_t alpha) {
if (!g_backbuffer || x < 0 || y < 0 || x >= g_width || y >= g_height) return;
uint32_t bg = g_backbuffer[y * g_width + x];
g_backbuffer[y * g_width + x] = gui_color_alpha_blend(bg, color, alpha);
}
void gui_gfx_draw_line(int x0, int y0, int x1, int y1, uint32_t color) {
int dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
int dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
while (1) {
gui_gfx_draw_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
}
void gui_gfx_draw_rect(int x, int y, int w, int h, uint32_t color) {
gui_gfx_draw_line(x, y, x + w - 1, y, color);
gui_gfx_draw_line(x, y + h - 1, x + w - 1, y + h - 1, color);
gui_gfx_draw_line(x, y, x, y + h - 1, color);
gui_gfx_draw_line(x + w - 1, y, x + w - 1, y + h - 1, color);
}
void gui_gfx_fill_rect(int x, int y, int w, int h, uint32_t color) {
if (!g_backbuffer) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > g_width) w = g_width - x;
if (y + h > g_height) h = g_height - y;
if (w <= 0 || h <= 0) return;
for (int j = y; j < y + h; j++) {
uint32_t* row = &g_backbuffer[j * g_width + x];
for (int i = 0; i < w; i++) {
row[i] = color;
}
}
}
void gui_gfx_fill_rect_blend(int x, int y, int w, int h, uint32_t color, uint8_t alpha) {
if (!g_backbuffer) return;
if (x < 0) { w += x; x = 0; }
if (y < 0) { h += y; y = 0; }
if (x + w > g_width) w = g_width - x;
if (y + h > g_height) h = g_height - y;
if (w <= 0 || h <= 0) return;
for (int j = y; j < y + h; j++) {
uint32_t* row = &g_backbuffer[j * g_width + x];
for (int i = 0; i < w; i++) {
row[i] = gui_color_alpha_blend(row[i], color, alpha);
}
}
}
void gui_gfx_draw_rounded_rect(int x, int y, int w, int h, int r, uint32_t color, bool filled) {
if (r <= 0) {
if (filled) gui_gfx_fill_rect(x, y, w, h, color);
else gui_gfx_draw_rect(x, y, w, h, color);
return;
}
if (filled) {
gui_gfx_fill_rect(x + r, y, w - 2 * r, h, color);
gui_gfx_fill_rect(x, y + r, r, h - 2 * r, color);
gui_gfx_fill_rect(x + w - r, y + r, r, h - 2 * r, color);
} else {
gui_gfx_draw_line(x + r, y, x + w - r, y, color);
gui_gfx_draw_line(x + r, y + h - 1, x + w - r, y + h - 1, color);
gui_gfx_draw_line(x, y + r, x, y + h - r, color);
gui_gfx_draw_line(x + w - 1, y + r, x + w - 1, y + h - r, color);
}
}
void gui_gfx_draw_gradient_v(int x, int y, int w, int h, uint32_t top_col, uint32_t bot_col) {
if (h <= 0 || w <= 0) return;
for (int j = 0; j < h; j++) {
uint8_t factor = (uint8_t)((j * 255) / h);
uint32_t row_col = gui_color_alpha_blend(top_col, bot_col, factor);
gui_gfx_fill_rect(x, y + j, w, 1, row_col);
}
}
void gui_gfx_draw_gradient_h(int x, int y, int w, int h, uint32_t left_col, uint32_t right_col) {
if (w <= 0 || h <= 0) return;
for (int i = 0; i < w; i++) {
uint8_t factor = (uint8_t)((i * 255) / w);
uint32_t col = gui_color_alpha_blend(left_col, right_col, factor);
gui_gfx_fill_rect(x + i, y, 1, h, col);
}
}
void gui_gfx_draw_char(int x, int y, char c, uint32_t fg, uint32_t bg, bool transparent_bg) {
if (c < 32 || c > 126) c = '?';
const uint8_t* glyph = font8x8[c - 32];
for (int row = 0; row < 8; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < 8; col++) {
if (bits & (1 << (7 - col))) {
gui_gfx_draw_pixel(x + col, y + row, fg);
} else if (!transparent_bg) {
gui_gfx_draw_pixel(x + col, y + row, bg);
}
}
}
}
// Sharp High-DPI 8x16 font renderer with 2x vertical pixel fidelity
void gui_gfx_draw_char_16(int x, int y, char c, uint32_t fg, uint32_t bg, bool transparent_bg) {
if (c < 32 || c > 126) c = '?';
const uint8_t* glyph = font8x8[c - 32];
for (int row = 0; row < 8; row++) {
uint8_t bits = glyph[row];
for (int col = 0; col < 8; col++) {
if (bits & (1 << (7 - col))) {
gui_gfx_draw_pixel(x + col, y + row * 2, fg);
gui_gfx_draw_pixel(x + col, y + row * 2 + 1, fg);
} else if (!transparent_bg) {
gui_gfx_draw_pixel(x + col, y + row * 2, bg);
gui_gfx_draw_pixel(x + col, y + row * 2 + 1, bg);
}
}
}
}
void gui_gfx_draw_string(int x, int y, const char* str, uint32_t fg) {
if (!str) return;
int cx = x;
while (*str) {
gui_gfx_draw_char(cx, y, *str, fg, 0, true);
cx += 8;
str++;
}
}
void gui_gfx_draw_string_16(int x, int y, const char* str, uint32_t fg) {
if (!str) return;
int cx = x;
while (*str) {
gui_gfx_draw_char_16(cx, y, *str, fg, 0, true);
cx += 8;
str++;
}
}
void gui_gfx_draw_string_bg(int x, int y, const char* str, uint32_t fg, uint32_t bg) {
if (!str) return;
int cx = x;
while (*str) {
gui_gfx_draw_char(cx, y, *str, fg, bg, false);
cx += 8;
str++;
}
}
void gui_gfx_draw_string_shadow(int x, int y, const char* str, uint32_t fg, uint32_t shadow) {
gui_gfx_draw_string(x + 1, y + 1, str, shadow);
gui_gfx_draw_string(x, y, str, fg);
}
void gui_gfx_draw_string_16_shadow(int x, int y, const char* str, uint32_t fg, uint32_t shadow) {
gui_gfx_draw_string_16(x + 1, y + 1, str, shadow);
gui_gfx_draw_string_16(x, y, str, fg);
}
void gui_gfx_draw_shadow(int x, int y, int w, int h, int blur) {
for (int r = 1; r <= blur; r++) {
uint8_t alpha = (uint8_t)(50 / r);
gui_gfx_fill_rect_blend(x + r, y + h, w, r, GUI_COLOR_BLACK, alpha);
gui_gfx_fill_rect_blend(x + w, y + r, r, h, GUI_COLOR_BLACK, alpha);
}
}
void gui_gfx_blit(int dx, int dy, int w, int h, const uint32_t* src_buf) {
if (!g_backbuffer || !src_buf) return;
for (int j = 0; j < h; j++) {
int py = dy + j;
if (py < 0 || py >= g_height) continue;
for (int i = 0; i < w; i++) {
int px = dx + i;
if (px < 0 || px >= g_width) continue;
uint32_t pixel = src_buf[j * w + i];
if ((pixel >> 24) > 0) {
g_backbuffer[py * g_width + px] = pixel;
}
}
}
}
void gui_gfx_present(void) {
if (g_frontbuffer && g_backbuffer) {
memcpy(g_frontbuffer, g_backbuffer, g_width * g_height * sizeof(uint32_t));
}
}