feat: TrueType (TTF) font rendering, many new desktop applications and DOOM support, among other improvements
This commit is contained in:
@@ -1,26 +1,12 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* clear - Clear terminal screen and framebuffer
|
||||
* clear - Clear terminal screen
|
||||
* Copyright (c) 2025-2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <zenith/syscall.h>
|
||||
|
||||
extern "C" void _start() {
|
||||
// Clear the raw framebuffer (needed after graphical programs like DOOM)
|
||||
Zenith::FbInfo fb;
|
||||
zenith::fb_info(&fb);
|
||||
uint8_t* pixels = (uint8_t*)zenith::fb_map();
|
||||
if (pixels) {
|
||||
for (uint64_t y = 0; y < fb.height; y++) {
|
||||
uint32_t* row = (uint32_t*)(pixels + y * fb.pitch);
|
||||
for (uint64_t x = 0; x < fb.width; x++) {
|
||||
row[x] = 0x00000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the text console
|
||||
zenith::print("\033[2J"); // Clear entire screen
|
||||
zenith::print("\033[H"); // Move cursor to top-left
|
||||
zenith::exit(0);
|
||||
|
||||
@@ -20,7 +20,7 @@ LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
|
||||
# ---- Compiler flags ----
|
||||
# ---- C++ compiler flags ----
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
@@ -40,10 +40,8 @@ CXXFLAGS := \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-mno-80387 \
|
||||
-mno-mmx \
|
||||
-mno-sse \
|
||||
-mno-sse2 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
@@ -61,9 +59,9 @@ LDFLAGS := \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- Source files ----
|
||||
# ---- C++ 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 app_settings.cpp
|
||||
SRCS := main.cpp font_data.cpp stb_truetype_impl.cpp app_terminal.cpp app_filemanager.cpp app_sysinfo.cpp app_calculator.cpp app_texteditor.cpp app_klog.cpp app_wiki.cpp app_procmgr.cpp app_mandelbrot.cpp app_devexplorer.cpp app_settings.cpp app_doom.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
@@ -78,6 +76,7 @@ $(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) -o $@
|
||||
|
||||
# C++ sources
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
@@ -231,10 +231,18 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) {
|
||||
c.fill_rect(0, 0, c.w, CALC_DISPLAY_H, Color::from_rgb(0x2D, 0x2D, 0x2D));
|
||||
|
||||
// Display text (right-aligned, 2x scale)
|
||||
int text_len = zenith::slen(cs->display_str);
|
||||
int text_w = text_len * FONT_WIDTH * 2;
|
||||
int text_w;
|
||||
int large_h;
|
||||
if (fonts::system_font && fonts::system_font->valid) {
|
||||
text_w = fonts::system_font->measure_text(cs->display_str, fonts::LARGE_SIZE);
|
||||
large_h = fonts::system_font->get_line_height(fonts::LARGE_SIZE);
|
||||
} else {
|
||||
int text_len = zenith::slen(cs->display_str);
|
||||
text_w = text_len * FONT_WIDTH * 2;
|
||||
large_h = FONT_HEIGHT * 2;
|
||||
}
|
||||
int tx = c.w - text_w - 12;
|
||||
int ty = (CALC_DISPLAY_H - FONT_HEIGHT * 2) / 2;
|
||||
int ty = (CALC_DISPLAY_H - large_h) / 2;
|
||||
if (tx < 4) tx = 4;
|
||||
c.text_2x(tx, ty, cs->display_str, colors::WHITE);
|
||||
|
||||
@@ -271,9 +279,9 @@ static void calculator_on_draw(Window* win, Framebuffer& fb) {
|
||||
// Button text
|
||||
const char* label = calc_labels[row][col];
|
||||
Color label_color = (col == 3) ? colors::WHITE : colors::TEXT_COLOR;
|
||||
int label_w = zenith::slen(label) * FONT_WIDTH;
|
||||
int label_w = text_width(label);
|
||||
int lx = bx + (bw - label_w) / 2;
|
||||
int ly = by + (CALC_BTN_H - FONT_HEIGHT) / 2;
|
||||
int ly = by + (CALC_BTN_H - system_font_height()) / 2;
|
||||
c.text(lx, ly, label, label_color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* app_devexplorer.cpp
|
||||
* ZenithOS Desktop - Device Explorer (lists hardware detected by the kernel)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Device Explorer state
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int DE_TOOLBAR_H = 36;
|
||||
static constexpr int DE_CAT_H = 28;
|
||||
static constexpr int DE_ITEM_H = 24;
|
||||
static constexpr int DE_MAX_DEVS = 64;
|
||||
static constexpr int DE_POLL_MS = 2000;
|
||||
static constexpr int DE_INDENT = 28;
|
||||
|
||||
// Category names matching DevInfo.category values
|
||||
static const char* category_names[] = {
|
||||
"CPU", // 0
|
||||
"Interrupt", // 1
|
||||
"Timer", // 2
|
||||
"Input", // 3
|
||||
"USB", // 4
|
||||
"Network", // 5
|
||||
"Display", // 6
|
||||
"PCI", // 7
|
||||
};
|
||||
static constexpr int NUM_CATEGORIES = 8;
|
||||
|
||||
static Color category_colors[] = {
|
||||
Color::from_rgb(0x33, 0x66, 0xCC), // CPU - blue
|
||||
Color::from_rgb(0x88, 0x44, 0xAA), // Interrupt - purple
|
||||
Color::from_rgb(0x22, 0x88, 0x22), // Timer - green
|
||||
Color::from_rgb(0xCC, 0x88, 0x00), // Input - amber
|
||||
Color::from_rgb(0x00, 0x88, 0x88), // USB - teal
|
||||
Color::from_rgb(0xCC, 0x55, 0x22), // Network - orange
|
||||
Color::from_rgb(0x44, 0x66, 0xCC), // Display - indigo
|
||||
Color::from_rgb(0x66, 0x66, 0x66), // PCI - gray
|
||||
};
|
||||
|
||||
struct DevExplorerState {
|
||||
DesktopState* desktop;
|
||||
Zenith::DevInfo devs[DE_MAX_DEVS];
|
||||
int dev_count;
|
||||
bool collapsed[NUM_CATEGORIES]; // per-category collapse state
|
||||
int selected_row; // index into visible display rows (-1 = none)
|
||||
int scroll_y; // scroll offset in display rows
|
||||
uint64_t last_poll_ms;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Display row model
|
||||
// ============================================================================
|
||||
|
||||
enum RowType { ROW_CATEGORY, ROW_DEVICE };
|
||||
|
||||
struct DisplayRow {
|
||||
RowType type;
|
||||
int category; // category index (0..7)
|
||||
int dev_index; // index into devs[] (only valid for ROW_DEVICE)
|
||||
};
|
||||
|
||||
static constexpr int MAX_DISPLAY_ROWS = DE_MAX_DEVS + NUM_CATEGORIES;
|
||||
|
||||
static int build_display_rows(DevExplorerState* de, DisplayRow* rows) {
|
||||
int count = 0;
|
||||
for (int cat = 0; cat < NUM_CATEGORIES; cat++) {
|
||||
// Count devices in this category
|
||||
int cat_count = 0;
|
||||
for (int d = 0; d < de->dev_count; d++) {
|
||||
if (de->devs[d].category == cat) cat_count++;
|
||||
}
|
||||
if (cat_count == 0) continue;
|
||||
|
||||
// Emit category header
|
||||
rows[count].type = ROW_CATEGORY;
|
||||
rows[count].category = cat;
|
||||
rows[count].dev_index = -1;
|
||||
count++;
|
||||
|
||||
// Emit device rows if expanded
|
||||
if (!de->collapsed[cat]) {
|
||||
for (int d = 0; d < de->dev_count; d++) {
|
||||
if (de->devs[d].category == cat) {
|
||||
rows[count].type = ROW_DEVICE;
|
||||
rows[count].category = cat;
|
||||
rows[count].dev_index = d;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Triangle drawing helpers
|
||||
// ============================================================================
|
||||
|
||||
static void draw_triangle_right(Canvas& c, int x, int y, int size, Color col) {
|
||||
// Right-pointing filled triangle (▶)
|
||||
int half = size / 2;
|
||||
for (int row = 0; row < size; row++) {
|
||||
int dist = (row <= half) ? row : (size - 1 - row);
|
||||
for (int col_px = 0; col_px <= dist; col_px++) {
|
||||
c.put_pixel(x + col_px, y + row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_triangle_down(Canvas& c, int x, int y, int size, Color col) {
|
||||
// Down-pointing filled triangle (▼)
|
||||
int half = size / 2;
|
||||
for (int row = 0; row < half + 1; row++) {
|
||||
int w = size - row * 2;
|
||||
for (int col_px = 0; col_px < w; col_px++) {
|
||||
c.put_pixel(x + row + col_px, y + row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
static void devexplorer_on_poll(Window* win) {
|
||||
DevExplorerState* de = (DevExplorerState*)win->app_data;
|
||||
if (!de) return;
|
||||
|
||||
uint64_t now = zenith::get_milliseconds();
|
||||
if (now - de->last_poll_ms < DE_POLL_MS) return;
|
||||
de->last_poll_ms = now;
|
||||
|
||||
de->dev_count = zenith::devlist(de->devs, DE_MAX_DEVS);
|
||||
}
|
||||
|
||||
static void devexplorer_on_draw(Window* win, Framebuffer& fb) {
|
||||
DevExplorerState* de = (DevExplorerState*)win->app_data;
|
||||
if (!de) return;
|
||||
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
int fh = system_font_height();
|
||||
|
||||
// --- Toolbar ---
|
||||
c.fill_rect(0, 0, c.w, DE_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, DE_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// "Refresh" button
|
||||
int btn_w = 80;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (DE_TOOLBAR_H - btn_h) / 2;
|
||||
c.button(btn_x, btn_y, btn_w, btn_h, "Refresh",
|
||||
Color::from_rgb(0x33, 0x66, 0xCC), colors::WHITE, 4);
|
||||
|
||||
// Device count on right
|
||||
char count_str[24];
|
||||
snprintf(count_str, sizeof(count_str), "%d devices", de->dev_count);
|
||||
int cw = text_width(count_str);
|
||||
c.text(c.w - cw - 12, (DE_TOOLBAR_H - fh) / 2, count_str, colors::TEXT_COLOR);
|
||||
|
||||
// --- Build display rows ---
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(de, rows);
|
||||
|
||||
// --- Compute visible area ---
|
||||
int list_y = DE_TOOLBAR_H;
|
||||
int list_h = c.h - list_y;
|
||||
if (list_h < 1) return;
|
||||
|
||||
// Clamp scroll
|
||||
// Calculate total content height
|
||||
int total_h = 0;
|
||||
for (int i = 0; i < row_count; i++) {
|
||||
total_h += (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
}
|
||||
int max_scroll_px = total_h - list_h;
|
||||
if (max_scroll_px < 0) max_scroll_px = 0;
|
||||
|
||||
// Convert scroll_y (in rows) to pixel offset for simplicity,
|
||||
// but keep the row-based model: scroll_y = row offset
|
||||
// We'll compute cumulative pixel offsets
|
||||
int scroll_px = 0;
|
||||
for (int i = 0; i < de->scroll_y && i < row_count; i++) {
|
||||
scroll_px += (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
}
|
||||
if (scroll_px > max_scroll_px) {
|
||||
scroll_px = max_scroll_px;
|
||||
// Recalculate scroll_y to match
|
||||
de->scroll_y = 0;
|
||||
int acc = 0;
|
||||
for (int i = 0; i < row_count; i++) {
|
||||
int rh = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
if (acc + rh > max_scroll_px) break;
|
||||
acc += rh;
|
||||
de->scroll_y = i + 1;
|
||||
}
|
||||
}
|
||||
if (de->scroll_y < 0) de->scroll_y = 0;
|
||||
|
||||
// Clamp selected_row
|
||||
if (de->selected_row >= row_count) de->selected_row = row_count - 1;
|
||||
|
||||
// --- Draw rows ---
|
||||
int draw_y = list_y - scroll_px;
|
||||
// Advance to first visible area by recomputing from scroll offset
|
||||
draw_y = list_y;
|
||||
int first_visible = de->scroll_y;
|
||||
|
||||
// Compute starting y by accumulating heights of skipped rows
|
||||
// Actually, let's just iterate all rows and skip those above the viewport
|
||||
draw_y = list_y;
|
||||
for (int i = 0; i < first_visible && i < row_count; i++) {
|
||||
draw_y -= (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
}
|
||||
|
||||
// Draw from first_visible onward
|
||||
int cur_y = list_y;
|
||||
for (int i = first_visible; i < row_count; i++) {
|
||||
int row_h = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
if (cur_y >= c.h) break; // Past bottom of window
|
||||
|
||||
if (rows[i].type == ROW_CATEGORY) {
|
||||
// Category header
|
||||
int cat = rows[i].category;
|
||||
c.fill_rect(0, cur_y, c.w, DE_CAT_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
// Highlight if selected
|
||||
if (i == de->selected_row) {
|
||||
c.fill_rect(0, cur_y, c.w, DE_CAT_H, colors::MENU_HOVER);
|
||||
}
|
||||
|
||||
// Expand/collapse triangle
|
||||
int tri_x = 10;
|
||||
int tri_y = cur_y + (DE_CAT_H - 8) / 2;
|
||||
Color tri_color = Color::from_rgb(0x55, 0x55, 0x55);
|
||||
if (de->collapsed[cat]) {
|
||||
draw_triangle_right(c, tri_x, tri_y, 8, tri_color);
|
||||
} else {
|
||||
draw_triangle_down(c, tri_x, tri_y, 8, tri_color);
|
||||
}
|
||||
|
||||
// Colored dot
|
||||
int dot_x = 24;
|
||||
int dot_y = cur_y + (DE_CAT_H - 8) / 2;
|
||||
Color cat_col = (cat >= 0 && cat < NUM_CATEGORIES)
|
||||
? category_colors[cat] : colors::TEXT_COLOR;
|
||||
c.fill_rounded_rect(dot_x, dot_y, 8, 8, 4, cat_col);
|
||||
|
||||
// Category name
|
||||
const char* cat_name = (cat >= 0 && cat < NUM_CATEGORIES)
|
||||
? category_names[cat] : "?";
|
||||
int text_y = cur_y + (DE_CAT_H - fh) / 2;
|
||||
c.text(36, text_y, cat_name, Color::from_rgb(0x33, 0x33, 0x33));
|
||||
|
||||
// Device count in parentheses on right
|
||||
int cat_count = 0;
|
||||
for (int d = 0; d < de->dev_count; d++) {
|
||||
if (de->devs[d].category == cat) cat_count++;
|
||||
}
|
||||
char cnt_buf[16];
|
||||
snprintf(cnt_buf, sizeof(cnt_buf), "(%d)", cat_count);
|
||||
int name_w = text_width(cat_name);
|
||||
c.text(36 + name_w + 8, text_y, cnt_buf,
|
||||
Color::from_rgb(0x88, 0x88, 0x88));
|
||||
|
||||
// Bottom border
|
||||
c.hline(0, cur_y + DE_CAT_H - 1, c.w, Color::from_rgb(0xE0, 0xE0, 0xE0));
|
||||
} else {
|
||||
// Device item row
|
||||
int di = rows[i].dev_index;
|
||||
|
||||
// Selected row highlight
|
||||
if (i == de->selected_row) {
|
||||
c.fill_rect(0, cur_y, c.w, DE_ITEM_H, colors::MENU_HOVER);
|
||||
}
|
||||
|
||||
int text_y = cur_y + (DE_ITEM_H - fh) / 2;
|
||||
|
||||
// Device name (indented)
|
||||
c.text(DE_INDENT + 10, text_y, de->devs[di].name, colors::TEXT_COLOR);
|
||||
|
||||
// Detail string (right column, gray)
|
||||
int detail_x = c.w / 2 + 20;
|
||||
c.text(detail_x, text_y, de->devs[di].detail,
|
||||
Color::from_rgb(0x66, 0x66, 0x66));
|
||||
}
|
||||
|
||||
cur_y += row_h;
|
||||
}
|
||||
|
||||
// --- Scrollbar ---
|
||||
if (total_h > list_h) {
|
||||
int sb_x = c.w - 6;
|
||||
int thumb_h = (list_h * list_h) / total_h;
|
||||
if (thumb_h < 20) thumb_h = 20;
|
||||
int thumb_y = list_y + (scroll_px * (list_h - thumb_h)) / max_scroll_px;
|
||||
c.fill_rect(sb_x, list_y, 4, list_h, Color::from_rgb(0xE0, 0xE0, 0xE0));
|
||||
c.fill_rect(sb_x, thumb_y, 4, thumb_h, Color::from_rgb(0xAA, 0xAA, 0xAA));
|
||||
}
|
||||
}
|
||||
|
||||
static void devexplorer_on_mouse(Window* win, MouseEvent& ev) {
|
||||
DevExplorerState* de = (DevExplorerState*)win->app_data;
|
||||
if (!de) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int lx = ev.x - cr.x;
|
||||
int ly = ev.y - cr.y;
|
||||
|
||||
// Scroll
|
||||
if (ev.scroll != 0) {
|
||||
de->scroll_y -= ev.scroll;
|
||||
if (de->scroll_y < 0) de->scroll_y = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.left_pressed()) {
|
||||
// Check "Refresh" button click
|
||||
int btn_w = 80;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (DE_TOOLBAR_H - btn_h) / 2;
|
||||
Rect btn_rect = {btn_x, btn_y, btn_w, btn_h};
|
||||
if (btn_rect.contains(lx, ly)) {
|
||||
de->last_poll_ms = 0; // force refresh
|
||||
return;
|
||||
}
|
||||
|
||||
// Check row click in list area
|
||||
int list_y = DE_TOOLBAR_H;
|
||||
if (ly >= list_y) {
|
||||
// Build display rows to map click to row
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(de, rows);
|
||||
|
||||
// Walk rows from scroll offset, accumulating y
|
||||
int cur_y = list_y;
|
||||
for (int i = de->scroll_y; i < row_count; i++) {
|
||||
int row_h = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
if (ly >= cur_y && ly < cur_y + row_h) {
|
||||
// Hit this row
|
||||
if (rows[i].type == ROW_CATEGORY) {
|
||||
// Toggle collapse
|
||||
int cat = rows[i].category;
|
||||
de->collapsed[cat] = !de->collapsed[cat];
|
||||
de->selected_row = -1;
|
||||
} else {
|
||||
de->selected_row = i;
|
||||
}
|
||||
return;
|
||||
}
|
||||
cur_y += row_h;
|
||||
if (cur_y >= win->content_h) break;
|
||||
}
|
||||
// Clicked below all rows
|
||||
de->selected_row = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void devexplorer_on_key(Window* win, const Zenith::KeyEvent& key) {
|
||||
DevExplorerState* de = (DevExplorerState*)win->app_data;
|
||||
if (!de || !key.pressed) return;
|
||||
|
||||
DisplayRow rows[MAX_DISPLAY_ROWS];
|
||||
int row_count = build_display_rows(de, rows);
|
||||
if (row_count == 0) return;
|
||||
|
||||
if (key.scancode == 0x48) { // Up arrow
|
||||
if (de->selected_row <= 0) {
|
||||
de->selected_row = 0;
|
||||
} else {
|
||||
de->selected_row--;
|
||||
}
|
||||
// Scroll to keep selection visible
|
||||
if (de->selected_row < de->scroll_y) {
|
||||
de->scroll_y = de->selected_row;
|
||||
}
|
||||
} else if (key.scancode == 0x50) { // Down arrow
|
||||
if (de->selected_row < row_count - 1) {
|
||||
de->selected_row++;
|
||||
}
|
||||
// Scroll to keep selection visible — estimate visible rows
|
||||
int list_h = win->content_h - DE_TOOLBAR_H;
|
||||
int cur_h = 0;
|
||||
int last_visible = de->scroll_y;
|
||||
for (int i = de->scroll_y; i < row_count; i++) {
|
||||
int rh = (rows[i].type == ROW_CATEGORY) ? DE_CAT_H : DE_ITEM_H;
|
||||
if (cur_h + rh > list_h) break;
|
||||
cur_h += rh;
|
||||
last_visible = i;
|
||||
}
|
||||
if (de->selected_row > last_visible) {
|
||||
de->scroll_y += (de->selected_row - last_visible);
|
||||
}
|
||||
} else if (key.scancode == 0x4B) { // Left arrow — collapse
|
||||
if (de->selected_row >= 0 && de->selected_row < row_count) {
|
||||
int cat = rows[de->selected_row].category;
|
||||
if (!de->collapsed[cat]) {
|
||||
de->collapsed[cat] = true;
|
||||
// If we were on a device row, move selection to the category header
|
||||
if (rows[de->selected_row].type == ROW_DEVICE) {
|
||||
// Find the category header row for this category
|
||||
for (int i = de->selected_row - 1; i >= 0; i--) {
|
||||
if (rows[i].type == ROW_CATEGORY && rows[i].category == cat) {
|
||||
de->selected_row = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// After collapsing, rebuild and clamp
|
||||
int new_count = build_display_rows(de, rows);
|
||||
if (de->selected_row >= new_count)
|
||||
de->selected_row = new_count - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (key.scancode == 0x4D) { // Right arrow — expand
|
||||
if (de->selected_row >= 0 && de->selected_row < row_count) {
|
||||
int cat = rows[de->selected_row].category;
|
||||
de->collapsed[cat] = false;
|
||||
}
|
||||
} else if (key.scancode == 0x1C) { // Enter — toggle on category row
|
||||
if (de->selected_row >= 0 && de->selected_row < row_count) {
|
||||
if (rows[de->selected_row].type == ROW_CATEGORY) {
|
||||
int cat = rows[de->selected_row].category;
|
||||
de->collapsed[cat] = !de->collapsed[cat];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void devexplorer_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
zenith::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Device Explorer launcher
|
||||
// ============================================================================
|
||||
|
||||
void open_devexplorer(DesktopState* ds) {
|
||||
int idx = desktop_create_window(ds, "Devices", 140, 70, 640, 460);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
|
||||
DevExplorerState* de = (DevExplorerState*)zenith::malloc(sizeof(DevExplorerState));
|
||||
zenith::memset(de, 0, sizeof(DevExplorerState));
|
||||
de->desktop = ds;
|
||||
de->selected_row = -1;
|
||||
de->scroll_y = 0;
|
||||
de->last_poll_ms = 0;
|
||||
|
||||
// All categories start expanded
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++)
|
||||
de->collapsed[i] = false;
|
||||
|
||||
// Initial poll
|
||||
de->dev_count = zenith::devlist(de->devs, DE_MAX_DEVS);
|
||||
|
||||
win->app_data = de;
|
||||
win->on_draw = devexplorer_on_draw;
|
||||
win->on_mouse = devexplorer_on_mouse;
|
||||
win->on_key = devexplorer_on_key;
|
||||
win->on_close = devexplorer_on_close;
|
||||
win->on_poll = devexplorer_on_poll;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* app_doom.cpp
|
||||
* ZenithOS Desktop - DOOM launcher (spawns standalone doom.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_doom(DesktopState* ds) {
|
||||
(void)ds;
|
||||
zenith::spawn("0:/games/doom.elf");
|
||||
}
|
||||
@@ -373,7 +373,7 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
|
||||
int tx = cell_x + (FM_GRID_CELL_W - tw) / 2;
|
||||
if (tx < cell_x) tx = cell_x;
|
||||
int ty = icon_y + FM_GRID_ICON + 2;
|
||||
if (ty >= list_y && ty + FONT_HEIGHT <= c.h)
|
||||
if (ty >= list_y && ty + system_font_height() <= c.h)
|
||||
c.text(tx, ty, label, colors::TEXT_COLOR);
|
||||
}
|
||||
} else {
|
||||
@@ -447,19 +447,20 @@ static void filemanager_on_draw(Window* win, Framebuffer& fb) {
|
||||
|
||||
// Name
|
||||
int tx = 30;
|
||||
int ty = iy + (FM_ITEM_H - FONT_HEIGHT) / 2;
|
||||
if (ty >= list_y && ty + FONT_HEIGHT <= c.h)
|
||||
int fm_sfh = system_font_height();
|
||||
int ty = iy + (FM_ITEM_H - fm_sfh) / 2;
|
||||
if (ty >= list_y && ty + fm_sfh <= c.h)
|
||||
c.text(tx, ty, fm->entry_names[i], colors::TEXT_COLOR);
|
||||
|
||||
// Size
|
||||
if (size_col_x > 100 && !fm->is_dir[i] && ty >= list_y && ty + FONT_HEIGHT <= c.h) {
|
||||
if (size_col_x > 100 && !fm->is_dir[i] && ty >= list_y && ty + fm_sfh <= c.h) {
|
||||
char size_str[16];
|
||||
format_size(size_str, fm->entry_sizes[i]);
|
||||
c.text(size_col_x, ty, size_str, dim);
|
||||
}
|
||||
|
||||
// Type
|
||||
if (type_col_x > 160 && ty >= list_y && ty + FONT_HEIGHT <= c.h) {
|
||||
if (type_col_x > 160 && ty >= list_y && ty + fm_sfh <= c.h) {
|
||||
const char* type_str = "File";
|
||||
if (fm->entry_types[i] == 1) type_str = "Dir";
|
||||
else if (fm->entry_types[i] == 2) type_str = "Exec";
|
||||
|
||||
@@ -119,8 +119,8 @@ void open_klog(DesktopState* ds) {
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
Rect cr = win->content_rect();
|
||||
int cols = cr.w / FONT_WIDTH;
|
||||
int rows = cr.h / FONT_HEIGHT;
|
||||
int cols = cr.w / mono_cell_width();
|
||||
int rows = cr.h / mono_cell_height();
|
||||
|
||||
KlogState* klog = (KlogState*)zenith::malloc(sizeof(KlogState));
|
||||
zenith::memset(klog, 0, sizeof(KlogState));
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* app_mandelbrot.cpp
|
||||
* ZenithOS Desktop - Mandelbrot set visualizer
|
||||
* Supports zoom (scroll wheel), pan (drag), and reset (R key)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Fixed-point Mandelbrot (avoids FPU/SSE dependency issues)
|
||||
// Uses 28.36 fixed-point: 36 fractional bits gives ~1e-10 precision
|
||||
// ============================================================================
|
||||
|
||||
using fp_t = int64_t;
|
||||
static constexpr int FP_SHIFT = 36;
|
||||
static constexpr fp_t FP_ONE = (fp_t)1 << FP_SHIFT;
|
||||
|
||||
static inline fp_t fp_from_int(int v) { return (fp_t)v << FP_SHIFT; }
|
||||
|
||||
static inline fp_t fp_mul(fp_t a, fp_t b) {
|
||||
// Split to avoid overflow: a * b >> SHIFT
|
||||
// Use 128-bit intermediate via compiler builtin
|
||||
__int128 r = (__int128)a * b;
|
||||
return (fp_t)(r >> FP_SHIFT);
|
||||
}
|
||||
|
||||
// fp_div for small numerators (avoids __divti3 by using 64-bit division)
|
||||
// Only valid when a is small enough that a << FP_SHIFT fits in int64_t
|
||||
static inline fp_t fp_div_small(int a, int b) {
|
||||
return ((fp_t)a << FP_SHIFT) / (fp_t)b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// State
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MB_MAX_ITER = 256;
|
||||
static constexpr int MB_TOOLBAR_H = 32;
|
||||
|
||||
struct MandelbrotState {
|
||||
DesktopState* desktop;
|
||||
fp_t center_x, center_y; // center of view in fractal coords
|
||||
fp_t scale; // units per pixel (fixed-point)
|
||||
int max_iter;
|
||||
bool needs_render;
|
||||
// Drag state
|
||||
bool dragging;
|
||||
int drag_start_x, drag_start_y;
|
||||
fp_t drag_start_cx, drag_start_cy;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Color palette
|
||||
// ============================================================================
|
||||
|
||||
static uint32_t mandelbrot_color(int iter, int max_iter) {
|
||||
if (iter >= max_iter) return 0xFF000000; // black for inside set
|
||||
|
||||
// Smooth cycling palette using bit manipulation
|
||||
int t = (iter * 7) & 0xFF;
|
||||
int phase = (iter * 7) >> 8;
|
||||
|
||||
uint8_t r, g, b;
|
||||
switch (phase % 6) {
|
||||
case 0: r = 255; g = (uint8_t)t; b = 0; break;
|
||||
case 1: r = (uint8_t)(255 - t); g = 255; b = 0; break;
|
||||
case 2: r = 0; g = 255; b = (uint8_t)t; break;
|
||||
case 3: r = 0; g = (uint8_t)(255 - t); b = 255; break;
|
||||
case 4: r = (uint8_t)t; g = 0; b = 255; break;
|
||||
default: r = 255; g = 0; b = (uint8_t)(255 - t); break;
|
||||
}
|
||||
|
||||
return 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render
|
||||
// ============================================================================
|
||||
|
||||
static void mandelbrot_render(MandelbrotState* mb, uint32_t* pixels, int w, int h) {
|
||||
int render_h = h - MB_TOOLBAR_H;
|
||||
if (render_h <= 0) return;
|
||||
|
||||
fp_t half_w = fp_mul(fp_from_int(w / 2), mb->scale);
|
||||
fp_t half_h = fp_mul(fp_from_int(render_h / 2), mb->scale);
|
||||
fp_t x_min = mb->center_x - half_w;
|
||||
fp_t y_min = mb->center_y - half_h;
|
||||
fp_t bailout = fp_from_int(4);
|
||||
|
||||
for (int py = 0; py < render_h; py++) {
|
||||
fp_t ci = y_min + fp_mul(fp_from_int(py), mb->scale);
|
||||
uint32_t* row = pixels + (py + MB_TOOLBAR_H) * w;
|
||||
|
||||
for (int px = 0; px < w; px++) {
|
||||
fp_t cr = x_min + fp_mul(fp_from_int(px), mb->scale);
|
||||
|
||||
fp_t zr = 0, zi = 0;
|
||||
int iter = 0;
|
||||
|
||||
while (iter < mb->max_iter) {
|
||||
fp_t zr2 = fp_mul(zr, zr);
|
||||
fp_t zi2 = fp_mul(zi, zi);
|
||||
if (zr2 + zi2 > bailout) break;
|
||||
fp_t new_zr = zr2 - zi2 + cr;
|
||||
zi = fp_mul(fp_from_int(2), fp_mul(zr, zi)) + ci;
|
||||
zr = new_zr;
|
||||
iter++;
|
||||
}
|
||||
|
||||
row[px] = mandelbrot_color(iter, mb->max_iter);
|
||||
}
|
||||
}
|
||||
|
||||
mb->needs_render = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
static void mb_on_draw(Window* win, Framebuffer& fb) {
|
||||
MandelbrotState* mb = (MandelbrotState*)win->app_data;
|
||||
if (!mb) return;
|
||||
|
||||
Canvas c(win);
|
||||
|
||||
if (mb->needs_render) {
|
||||
mandelbrot_render(mb, win->content, c.w, c.h);
|
||||
}
|
||||
|
||||
// Draw toolbar over the render
|
||||
c.fill_rect(0, 0, c.w, MB_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, MB_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// Reset button
|
||||
c.button(8, 4, 60, 24, "Reset", colors::ACCENT, colors::WHITE, 4);
|
||||
|
||||
// Iter +/- buttons
|
||||
char iter_str[24];
|
||||
snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter);
|
||||
int fh = system_font_height();
|
||||
c.text(80, (MB_TOOLBAR_H - fh) / 2, iter_str, colors::TEXT_COLOR);
|
||||
|
||||
int iter_text_w = text_width(iter_str);
|
||||
int btn_x = 80 + iter_text_w + 8;
|
||||
c.button(btn_x, 4, 24, 24, "-", Color::from_rgb(0xAA, 0xAA, 0xAA), colors::WHITE, 4);
|
||||
c.button(btn_x + 28, 4, 24, 24, "+", Color::from_rgb(0xAA, 0xAA, 0xAA), colors::WHITE, 4);
|
||||
|
||||
// Hint
|
||||
const char* hint = "Scroll=zoom Drag=pan";
|
||||
int hw = text_width(hint);
|
||||
c.text(c.w - hw - 8, (MB_TOOLBAR_H - fh) / 2, hint, Color::from_rgb(0x99, 0x99, 0x99));
|
||||
}
|
||||
|
||||
static void mb_on_mouse(Window* win, MouseEvent& ev) {
|
||||
MandelbrotState* mb = (MandelbrotState*)win->app_data;
|
||||
if (!mb) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int lx = ev.x - cr.x;
|
||||
int ly = ev.y - cr.y;
|
||||
|
||||
// Handle toolbar clicks
|
||||
if (ev.left_pressed() && ly < MB_TOOLBAR_H) {
|
||||
// Reset button
|
||||
Rect reset_r = {8, 4, 60, 24};
|
||||
if (reset_r.contains(lx, ly)) {
|
||||
mb->center_x = -fp_from_int(1) / 2; // -0.5
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Iter buttons
|
||||
char iter_str[24];
|
||||
snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter);
|
||||
int iter_text_w = text_width(iter_str);
|
||||
int btn_x = 80 + iter_text_w + 8;
|
||||
|
||||
Rect minus_r = {btn_x, 4, 24, 24};
|
||||
Rect plus_r = {btn_x + 28, 4, 24, 24};
|
||||
|
||||
if (minus_r.contains(lx, ly)) {
|
||||
if (mb->max_iter > 32) { mb->max_iter /= 2; mb->needs_render = true; }
|
||||
return;
|
||||
}
|
||||
if (plus_r.contains(lx, ly)) {
|
||||
if (mb->max_iter < 4096) { mb->max_iter *= 2; mb->needs_render = true; }
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Drag panning
|
||||
if (ev.left_pressed() && ly >= MB_TOOLBAR_H) {
|
||||
mb->dragging = true;
|
||||
mb->drag_start_x = lx;
|
||||
mb->drag_start_y = ly;
|
||||
mb->drag_start_cx = mb->center_x;
|
||||
mb->drag_start_cy = mb->center_y;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mb->dragging && ev.left_held()) {
|
||||
int dx = lx - mb->drag_start_x;
|
||||
int dy = ly - mb->drag_start_y;
|
||||
mb->center_x = mb->drag_start_cx - fp_mul(fp_from_int(dx), mb->scale);
|
||||
mb->center_y = mb->drag_start_cy - fp_mul(fp_from_int(dy), mb->scale);
|
||||
mb->needs_render = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mb->dragging && !ev.left_held()) {
|
||||
mb->dragging = false;
|
||||
}
|
||||
|
||||
// Scroll zoom (centered on mouse position)
|
||||
if (ev.scroll != 0 && ly >= MB_TOOLBAR_H) {
|
||||
int render_h = cr.h - MB_TOOLBAR_H;
|
||||
// Mouse position in fractal coords before zoom
|
||||
fp_t mx_frac = mb->center_x + fp_mul(fp_from_int(lx - cr.w / 2), mb->scale);
|
||||
fp_t my_frac = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale);
|
||||
|
||||
if (ev.scroll < 0) {
|
||||
// Zoom in
|
||||
mb->scale = fp_mul(mb->scale, fp_from_int(3) / 4); // * 0.75
|
||||
} else {
|
||||
// Zoom out
|
||||
mb->scale = fp_mul(mb->scale, fp_from_int(4) / 3); // * 1.33
|
||||
}
|
||||
|
||||
// Adjust center so mouse stays at same fractal point
|
||||
fp_t new_mx = mb->center_x + fp_mul(fp_from_int(lx - cr.w / 2), mb->scale);
|
||||
fp_t new_my = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale);
|
||||
mb->center_x += mx_frac - new_mx;
|
||||
mb->center_y += my_frac - new_my;
|
||||
|
||||
mb->needs_render = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void mb_on_key(Window* win, const Zenith::KeyEvent& key) {
|
||||
MandelbrotState* mb = (MandelbrotState*)win->app_data;
|
||||
if (!mb || !key.pressed) return;
|
||||
|
||||
if (key.ascii == 'r' || key.ascii == 'R') {
|
||||
Rect cr = win->content_rect();
|
||||
mb->center_x = -fp_from_int(1) / 2;
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
} else if (key.ascii == '+' || key.ascii == '=') {
|
||||
if (mb->max_iter < 4096) { mb->max_iter *= 2; mb->needs_render = true; }
|
||||
} else if (key.ascii == '-') {
|
||||
if (mb->max_iter > 32) { mb->max_iter /= 2; mb->needs_render = true; }
|
||||
}
|
||||
}
|
||||
|
||||
static void mb_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
zenith::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mandelbrot launcher
|
||||
// ============================================================================
|
||||
|
||||
void open_mandelbrot(DesktopState* ds) {
|
||||
int idx = desktop_create_window(ds, "Mandelbrot", 120, 60, 500, 400);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
Rect cr = win->content_rect();
|
||||
|
||||
MandelbrotState* mb = (MandelbrotState*)zenith::malloc(sizeof(MandelbrotState));
|
||||
zenith::memset(mb, 0, sizeof(MandelbrotState));
|
||||
mb->desktop = ds;
|
||||
mb->center_x = -fp_from_int(1) / 2; // -0.5
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
mb->dragging = false;
|
||||
|
||||
win->app_data = mb;
|
||||
win->on_draw = mb_on_draw;
|
||||
win->on_mouse = mb_on_mouse;
|
||||
win->on_key = mb_on_key;
|
||||
win->on_close = mb_on_close;
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* app_procmgr.cpp
|
||||
* ZenithOS Desktop - Process Manager
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Process Manager state
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int PM_TOOLBAR_H = 36;
|
||||
static constexpr int PM_HEADER_H = 24;
|
||||
static constexpr int PM_ITEM_H = 28;
|
||||
static constexpr int PM_MAX_PROCS = 16;
|
||||
static constexpr int PM_POLL_MS = 1000;
|
||||
|
||||
struct ProcMgrState {
|
||||
DesktopState* desktop;
|
||||
Zenith::ProcInfo procs[PM_MAX_PROCS];
|
||||
int proc_count;
|
||||
int selected; // selected row index (-1 = none)
|
||||
uint64_t last_poll_ms;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
static void procmgr_on_poll(Window* win) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm) return;
|
||||
|
||||
uint64_t now = zenith::get_milliseconds();
|
||||
if (now - pm->last_poll_ms < PM_POLL_MS) return;
|
||||
pm->last_poll_ms = now;
|
||||
|
||||
// Remember previously selected PID
|
||||
int prev_pid = -1;
|
||||
if (pm->selected >= 0 && pm->selected < pm->proc_count) {
|
||||
prev_pid = pm->procs[pm->selected].pid;
|
||||
}
|
||||
|
||||
pm->proc_count = zenith::proclist(pm->procs, PM_MAX_PROCS);
|
||||
|
||||
// Restore selection by matching PID
|
||||
pm->selected = -1;
|
||||
if (prev_pid >= 0) {
|
||||
for (int i = 0; i < pm->proc_count; i++) {
|
||||
if (pm->procs[i].pid == prev_pid) {
|
||||
pm->selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_draw(Window* win, Framebuffer& fb) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm) return;
|
||||
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
int fh = system_font_height();
|
||||
|
||||
// --- Toolbar ---
|
||||
c.fill_rect(0, 0, c.w, PM_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, PM_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// "End Process" button
|
||||
int btn_w = 100;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (PM_TOOLBAR_H - btn_h) / 2;
|
||||
Color btn_bg = (pm->selected >= 0 && pm->procs[pm->selected].pid != 0)
|
||||
? Color::from_rgb(0xCC, 0x33, 0x33)
|
||||
: Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
c.button(btn_x, btn_y, btn_w, btn_h, "End Process", btn_bg, colors::WHITE, 4);
|
||||
|
||||
// Process count on right
|
||||
char count_str[24];
|
||||
snprintf(count_str, sizeof(count_str), "%d processes", pm->proc_count);
|
||||
int cw = text_width(count_str);
|
||||
c.text(c.w - cw - 12, (PM_TOOLBAR_H - fh) / 2, count_str, colors::TEXT_COLOR);
|
||||
|
||||
// --- Header row ---
|
||||
int header_y = PM_TOOLBAR_H;
|
||||
c.fill_rect(0, header_y, c.w, PM_HEADER_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
int col_pid = 12;
|
||||
int col_name = 64;
|
||||
int col_mem = c.w - 120;
|
||||
int col_state = c.w - 52;
|
||||
|
||||
int ty = header_y + (PM_HEADER_H - fh) / 2;
|
||||
c.text(col_pid, ty, "PID", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
c.text(col_name, ty, "Name", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
c.text(col_mem, ty, "Mem", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
c.text(col_state, ty, "St", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
|
||||
c.hline(0, header_y + PM_HEADER_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// --- Process rows ---
|
||||
int list_y = PM_TOOLBAR_H + PM_HEADER_H;
|
||||
for (int i = 0; i < pm->proc_count; i++) {
|
||||
int row_y = list_y + i * PM_ITEM_H;
|
||||
if (row_y + PM_ITEM_H > c.h) break;
|
||||
|
||||
// Selected row highlight
|
||||
if (i == pm->selected) {
|
||||
c.fill_rect(0, row_y, c.w, PM_ITEM_H, colors::MENU_HOVER);
|
||||
}
|
||||
|
||||
int ry = row_y + (PM_ITEM_H - fh) / 2;
|
||||
|
||||
// PID (right-aligned in PID column)
|
||||
char pid_str[8];
|
||||
snprintf(pid_str, sizeof(pid_str), "%d", (int)pm->procs[i].pid);
|
||||
int pid_w = text_width(pid_str);
|
||||
c.text(col_pid + 32 - pid_w, ry, pid_str, colors::TEXT_COLOR);
|
||||
|
||||
// Name (truncated to fit)
|
||||
c.text(col_name, ry, pm->procs[i].name, colors::TEXT_COLOR);
|
||||
|
||||
// Memory
|
||||
char mem_str[16];
|
||||
format_size(mem_str, (int)pm->procs[i].heapUsed);
|
||||
c.text(col_mem, ry, mem_str, colors::TEXT_COLOR);
|
||||
|
||||
// State
|
||||
const char* st_str;
|
||||
Color st_color;
|
||||
switch (pm->procs[i].state) {
|
||||
case 2: // Running
|
||||
st_str = "Run";
|
||||
st_color = Color::from_rgb(0x22, 0x88, 0x22);
|
||||
break;
|
||||
case 1: // Ready
|
||||
st_str = "Rdy";
|
||||
st_color = Color::from_rgb(0x33, 0x66, 0xCC);
|
||||
break;
|
||||
case 3: // Terminated
|
||||
st_str = "Term";
|
||||
st_color = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
break;
|
||||
default:
|
||||
st_str = "?";
|
||||
st_color = colors::TEXT_COLOR;
|
||||
break;
|
||||
}
|
||||
c.text(col_state, ry, st_str, st_color);
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_mouse(Window* win, MouseEvent& ev) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int lx = ev.x - cr.x;
|
||||
int ly = ev.y - cr.y;
|
||||
|
||||
if (ev.left_pressed()) {
|
||||
// Check "End Process" button click
|
||||
int btn_w = 100;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (PM_TOOLBAR_H - btn_h) / 2;
|
||||
Rect btn_rect = {btn_x, btn_y, btn_w, btn_h};
|
||||
if (btn_rect.contains(lx, ly)) {
|
||||
if (pm->selected >= 0 && pm->selected < pm->proc_count
|
||||
&& pm->procs[pm->selected].pid != 0) {
|
||||
zenith::kill(pm->procs[pm->selected].pid);
|
||||
pm->last_poll_ms = 0; // force refresh
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check row click
|
||||
int list_y = PM_TOOLBAR_H + PM_HEADER_H;
|
||||
if (ly >= list_y) {
|
||||
int row = (ly - list_y) / PM_ITEM_H;
|
||||
if (row >= 0 && row < pm->proc_count) {
|
||||
pm->selected = row;
|
||||
} else {
|
||||
pm->selected = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_key(Window* win, const Zenith::KeyEvent& key) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm || !key.pressed) return;
|
||||
|
||||
if (key.scancode == 0x48) { // Up arrow
|
||||
if (pm->selected > 0) pm->selected--;
|
||||
else if (pm->proc_count > 0) pm->selected = 0;
|
||||
} else if (key.scancode == 0x50) { // Down arrow
|
||||
if (pm->selected < pm->proc_count - 1) pm->selected++;
|
||||
} else if (key.scancode == 0x53) { // Delete key
|
||||
if (pm->selected >= 0 && pm->selected < pm->proc_count
|
||||
&& pm->procs[pm->selected].pid != 0) {
|
||||
zenith::kill(pm->procs[pm->selected].pid);
|
||||
pm->last_poll_ms = 0; // force refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
zenith::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Process Manager launcher
|
||||
// ============================================================================
|
||||
|
||||
void open_procmgr(DesktopState* ds) {
|
||||
int idx = desktop_create_window(ds, "Processes", 180, 80, 520, 400);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
|
||||
ProcMgrState* pm = (ProcMgrState*)zenith::malloc(sizeof(ProcMgrState));
|
||||
zenith::memset(pm, 0, sizeof(ProcMgrState));
|
||||
pm->desktop = ds;
|
||||
pm->selected = -1;
|
||||
pm->last_poll_ms = 0;
|
||||
|
||||
// Initial poll
|
||||
pm->proc_count = zenith::proclist(pm->procs, PM_MAX_PROCS);
|
||||
|
||||
win->app_data = pm;
|
||||
win->on_draw = procmgr_on_draw;
|
||||
win->on_mouse = procmgr_on_mouse;
|
||||
win->on_key = procmgr_on_key;
|
||||
win->on_close = procmgr_on_close;
|
||||
win->on_poll = procmgr_on_poll;
|
||||
}
|
||||
@@ -1,38 +1,252 @@
|
||||
/*
|
||||
* app_settings.cpp
|
||||
* ZenithOS Desktop - About application
|
||||
* ZenithOS Desktop - Settings application
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// About state and callbacks
|
||||
// Settings state
|
||||
// ============================================================================
|
||||
|
||||
struct AboutState {
|
||||
struct SettingsState {
|
||||
DesktopState* desktop;
|
||||
int active_tab; // 0=Appearance, 1=Display, 2=About
|
||||
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;
|
||||
// ============================================================================
|
||||
// Color palette presets
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int SWATCH_COUNT = 8;
|
||||
static constexpr int SWATCH_SIZE = 24;
|
||||
static constexpr int SWATCH_GAP = 6;
|
||||
|
||||
// Background colors (light tones)
|
||||
static const Color bg_palette[SWATCH_COUNT] = {
|
||||
Color::from_rgb(0xD0, 0xD8, 0xE8), // light blue-gray (default)
|
||||
Color::from_rgb(0xE8, 0xDD, 0xCB), // warm beige
|
||||
Color::from_rgb(0xC8, 0xE6, 0xD0), // mint green
|
||||
Color::from_rgb(0xD8, 0xD0, 0xE8), // lavender
|
||||
Color::from_rgb(0xB8, 0xBE, 0xC8), // slate
|
||||
Color::from_rgb(0xF0, 0xF0, 0xF0), // white
|
||||
Color::from_rgb(0xE8, 0xD0, 0xD8), // soft pink
|
||||
Color::from_rgb(0xE8, 0xE0, 0xC8), // light gold
|
||||
};
|
||||
|
||||
// Panel colors (dark tones)
|
||||
static const Color panel_palette[SWATCH_COUNT] = {
|
||||
Color::from_rgb(0x2B, 0x3E, 0x50), // dark blue-gray (default)
|
||||
Color::from_rgb(0x2D, 0x2D, 0x2D), // dark charcoal
|
||||
Color::from_rgb(0x1B, 0x2A, 0x4A), // navy
|
||||
Color::from_rgb(0x1A, 0x3A, 0x3A), // dark teal
|
||||
Color::from_rgb(0x1A, 0x3A, 0x1A), // dark green
|
||||
Color::from_rgb(0x30, 0x20, 0x40), // dark purple
|
||||
Color::from_rgb(0x40, 0x1A, 0x1A), // dark red
|
||||
Color::from_rgb(0x10, 0x10, 0x10), // black
|
||||
};
|
||||
|
||||
// Accent colors
|
||||
static const Color accent_palette[SWATCH_COUNT] = {
|
||||
Color::from_rgb(0x36, 0x7B, 0xF0), // blue (default)
|
||||
Color::from_rgb(0x00, 0x9B, 0x9B), // teal
|
||||
Color::from_rgb(0x2E, 0x9E, 0x3E), // green
|
||||
Color::from_rgb(0xE0, 0x8A, 0x20), // orange
|
||||
Color::from_rgb(0xD0, 0x3E, 0x3E), // red
|
||||
Color::from_rgb(0x7B, 0x3E, 0xB8), // purple
|
||||
Color::from_rgb(0xD0, 0x5C, 0x9E), // pink
|
||||
Color::from_rgb(0x44, 0x44, 0xCC), // indigo
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Layout constants
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int TAB_BAR_H = 36;
|
||||
static constexpr int TAB_COUNT = 3;
|
||||
static const char* tab_labels[TAB_COUNT] = { "Appearance", "Display", "About" };
|
||||
|
||||
// ============================================================================
|
||||
// Helper: check if two colors match
|
||||
// ============================================================================
|
||||
|
||||
static bool color_eq(Color a, Color b) {
|
||||
return a.r == b.r && a.g == b.g && a.b == b.b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper: find selected swatch index in a palette
|
||||
// ============================================================================
|
||||
|
||||
static int find_swatch(const Color* palette, Color current) {
|
||||
for (int i = 0; i < SWATCH_COUNT; i++) {
|
||||
if (color_eq(palette[i], current)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Drawing
|
||||
// ============================================================================
|
||||
|
||||
static void draw_swatch_row(Canvas& c, int x, int y, const Color* palette,
|
||||
Color selected, Color accent) {
|
||||
int sel = find_swatch(palette, selected);
|
||||
for (int i = 0; i < SWATCH_COUNT; i++) {
|
||||
int sx = x + i * (SWATCH_SIZE + SWATCH_GAP);
|
||||
// Draw selection border
|
||||
if (i == sel) {
|
||||
c.fill_rounded_rect(sx - 2, y - 2, SWATCH_SIZE + 4, SWATCH_SIZE + 4, 4, accent);
|
||||
}
|
||||
c.fill_rounded_rect(sx, y, SWATCH_SIZE, SWATCH_SIZE, 3, palette[i]);
|
||||
// Thin border for light colors
|
||||
c.rect(sx, y, SWATCH_SIZE, SWATCH_SIZE, Color::from_rgb(0xCC, 0xCC, 0xCC));
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_radio(Canvas& c, int x, int y, bool selected, Color accent) {
|
||||
int r = 7;
|
||||
// Outer circle (simple square approximation with rounded rect)
|
||||
c.fill_rounded_rect(x, y, r * 2, r * 2, r, Color::from_rgb(0xCC, 0xCC, 0xCC));
|
||||
c.fill_rounded_rect(x + 1, y + 1, r * 2 - 2, r * 2 - 2, r - 1, colors::WHITE);
|
||||
if (selected) {
|
||||
c.fill_rounded_rect(x + 4, y + 4, r * 2 - 8, r * 2 - 8, r - 4, accent);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_toggle_btn(Canvas& c, int x, int y, int bw, int bh,
|
||||
const char* label, bool active, Color accent) {
|
||||
Color bg = active ? accent : colors::WINDOW_BG;
|
||||
Color fg = active ? colors::WHITE : colors::TEXT_COLOR;
|
||||
c.fill_rounded_rect(x, y, bw, bh, 4, bg);
|
||||
if (!active) {
|
||||
c.rect(x, y, bw, bh, colors::BORDER);
|
||||
}
|
||||
int tw = text_width(label);
|
||||
int fh = system_font_height();
|
||||
c.text(x + (bw - tw) / 2, y + (bh - fh) / 2, label, fg);
|
||||
}
|
||||
|
||||
static void settings_draw_appearance(Canvas& c, SettingsState* st) {
|
||||
DesktopSettings& s = st->desktop->settings;
|
||||
Color accent = s.accent_color;
|
||||
int x = 16;
|
||||
int y = 12;
|
||||
int sfh = system_font_height();
|
||||
int line_h = sfh + 10;
|
||||
|
||||
// Section: Background
|
||||
c.text(x, y, "Background", colors::TEXT_COLOR);
|
||||
y += line_h;
|
||||
|
||||
// Radio buttons: Gradient / Solid
|
||||
draw_radio(c, x, y, s.bg_gradient, accent);
|
||||
c.text(x + 20, y + 2, "Gradient", colors::TEXT_COLOR);
|
||||
|
||||
draw_radio(c, x + 120, y, !s.bg_gradient, accent);
|
||||
c.text(x + 140, y + 2, "Solid Color", colors::TEXT_COLOR);
|
||||
y += line_h + 4;
|
||||
|
||||
if (s.bg_gradient) {
|
||||
// Top color swatches
|
||||
c.text(x, y + 4, "Top", Color::from_rgb(0x88, 0x88, 0x88));
|
||||
draw_swatch_row(c, x + 70, y, bg_palette, s.bg_grad_top, accent);
|
||||
y += SWATCH_SIZE + 14;
|
||||
|
||||
// Bottom color swatches
|
||||
c.text(x, y + 4, "Bottom", Color::from_rgb(0x88, 0x88, 0x88));
|
||||
draw_swatch_row(c, x + 70, y, bg_palette, s.bg_grad_bottom, accent);
|
||||
y += SWATCH_SIZE + 14;
|
||||
} else {
|
||||
// Solid color swatches
|
||||
c.text(x, y + 4, "Color", Color::from_rgb(0x88, 0x88, 0x88));
|
||||
draw_swatch_row(c, x + 70, y, bg_palette, s.bg_solid, accent);
|
||||
y += SWATCH_SIZE + 14;
|
||||
}
|
||||
|
||||
// Separator
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
y += 12;
|
||||
|
||||
// Panel color
|
||||
c.text(x, y + 4, "Panel Color", colors::TEXT_COLOR);
|
||||
draw_swatch_row(c, x + 110, y, panel_palette, s.panel_color, accent);
|
||||
y += SWATCH_SIZE + 14;
|
||||
|
||||
// Separator
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
y += 12;
|
||||
|
||||
// Accent color
|
||||
c.text(x, y + 4, "Accent Color", colors::TEXT_COLOR);
|
||||
draw_swatch_row(c, x + 110, y, accent_palette, s.accent_color, accent);
|
||||
}
|
||||
|
||||
static void apply_ui_scale(int scale) {
|
||||
switch (scale) {
|
||||
case 0: fonts::UI_SIZE=14; fonts::TITLE_SIZE=14; fonts::TERM_SIZE=14; fonts::LARGE_SIZE=22; break;
|
||||
case 2: fonts::UI_SIZE=22; fonts::TITLE_SIZE=22; fonts::TERM_SIZE=22; fonts::LARGE_SIZE=34; break;
|
||||
default: fonts::UI_SIZE=18; fonts::TITLE_SIZE=18; fonts::TERM_SIZE=18; fonts::LARGE_SIZE=28; break;
|
||||
}
|
||||
}
|
||||
|
||||
static void settings_draw_display(Canvas& c, SettingsState* st) {
|
||||
DesktopSettings& s = st->desktop->settings;
|
||||
Color accent = s.accent_color;
|
||||
int x = 16;
|
||||
int y = 20;
|
||||
int btn_w = 60;
|
||||
int btn_h = 28;
|
||||
|
||||
// Window Shadows
|
||||
c.text(x, y + 6, "Window Shadows", colors::TEXT_COLOR);
|
||||
int bx = x + 180;
|
||||
draw_toggle_btn(c, bx, y, btn_w, btn_h, "On", s.show_shadows, accent);
|
||||
draw_toggle_btn(c, bx + btn_w + 8, y, btn_w, btn_h, "Off", !s.show_shadows, accent);
|
||||
y += btn_h + 20;
|
||||
|
||||
// Separator
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
y += 16;
|
||||
|
||||
// Clock Format
|
||||
c.text(x, y + 6, "Clock Format", colors::TEXT_COLOR);
|
||||
bx = x + 180;
|
||||
draw_toggle_btn(c, bx, y, btn_w, btn_h, "24h", s.clock_24h, accent);
|
||||
draw_toggle_btn(c, bx + btn_w + 8, y, btn_w, btn_h, "12h", !s.clock_24h, accent);
|
||||
y += btn_h + 20;
|
||||
|
||||
// Separator
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
y += 16;
|
||||
|
||||
// UI Scale
|
||||
c.text(x, y + 6, "UI Scale", colors::TEXT_COLOR);
|
||||
bx = x + 180;
|
||||
int sbw = 68;
|
||||
draw_toggle_btn(c, bx, y, sbw, btn_h, "Small", s.ui_scale == 0, accent);
|
||||
draw_toggle_btn(c, bx + sbw + 8, y, sbw, btn_h, "Default", s.ui_scale == 1, accent);
|
||||
draw_toggle_btn(c, bx + (sbw + 8) * 2, y, sbw, btn_h, "Large", s.ui_scale == 2, accent);
|
||||
}
|
||||
|
||||
static void settings_draw_about(Canvas& c, SettingsState* st) {
|
||||
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;
|
||||
int sfh = system_font_height();
|
||||
int line_h = sfh + 6;
|
||||
|
||||
// OS name in 2x size
|
||||
c.text_2x(x, y, st->sys_info.osName, colors::ACCENT);
|
||||
y += FONT_HEIGHT * 2 + 8;
|
||||
c.text_2x(x, y, st->sys_info.osName, st->desktop->settings.accent_color);
|
||||
int large_h = (fonts::system_font && fonts::system_font->valid)
|
||||
? fonts::system_font->get_line_height(fonts::LARGE_SIZE) : (FONT_HEIGHT * 2);
|
||||
y += large_h + 8;
|
||||
|
||||
snprintf(line, sizeof(line), "Version %s", st->sys_info.osVersion);
|
||||
c.text(x, y, line, colors::TEXT_COLOR);
|
||||
@@ -41,27 +255,231 @@ static void about_on_draw(Window* win, Framebuffer& fb) {
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
y += 12;
|
||||
|
||||
snprintf(line, sizeof(line), "API version: %d", (int)st->sys_info.apiVersion);
|
||||
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);
|
||||
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__);
|
||||
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) {
|
||||
static void settings_on_draw(Window* win, Framebuffer& fb) {
|
||||
SettingsState* st = (SettingsState*)win->app_data;
|
||||
if (!st) return;
|
||||
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
Color accent = st->desktop->settings.accent_color;
|
||||
int sfh = system_font_height();
|
||||
|
||||
// Draw tab bar background
|
||||
c.fill_rect(0, 0, c.w, TAB_BAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, TAB_BAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// Draw tabs
|
||||
int tab_w = c.w / TAB_COUNT;
|
||||
for (int i = 0; i < TAB_COUNT; i++) {
|
||||
int tx = i * tab_w;
|
||||
bool active = (i == st->active_tab);
|
||||
|
||||
if (active) {
|
||||
c.fill_rect(tx, 0, tab_w, TAB_BAR_H, colors::WINDOW_BG);
|
||||
// Active tab underline
|
||||
c.fill_rect(tx + 4, TAB_BAR_H - 3, tab_w - 8, 3, accent);
|
||||
}
|
||||
|
||||
int tw = text_width(tab_labels[i]);
|
||||
Color tc = active ? accent : Color::from_rgb(0x66, 0x66, 0x66);
|
||||
c.text(tx + (tab_w - tw) / 2, (TAB_BAR_H - sfh) / 2, tab_labels[i], tc);
|
||||
}
|
||||
|
||||
// Draw tab content below the tab bar
|
||||
// Create a sub-canvas offset by TAB_BAR_H
|
||||
Canvas content(win->content + TAB_BAR_H * win->content_w,
|
||||
win->content_w, win->content_h - TAB_BAR_H);
|
||||
|
||||
switch (st->active_tab) {
|
||||
case 0: settings_draw_appearance(content, st); break;
|
||||
case 1: settings_draw_display(content, st); break;
|
||||
case 2: settings_draw_about(content, st); break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mouse interaction
|
||||
// ============================================================================
|
||||
|
||||
static bool swatch_hit(int mx, int my, int row_x, int row_y, int* out_idx) {
|
||||
for (int i = 0; i < SWATCH_COUNT; i++) {
|
||||
int sx = row_x + i * (SWATCH_SIZE + SWATCH_GAP);
|
||||
if (mx >= sx && mx < sx + SWATCH_SIZE && my >= row_y && my < row_y + SWATCH_SIZE) {
|
||||
*out_idx = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void settings_on_mouse(Window* win, MouseEvent& ev) {
|
||||
SettingsState* st = (SettingsState*)win->app_data;
|
||||
if (!st) return;
|
||||
|
||||
if (!ev.left_pressed()) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int mx = ev.x - cr.x;
|
||||
int my = ev.y - cr.y;
|
||||
|
||||
// Tab bar click
|
||||
if (my >= 0 && my < TAB_BAR_H) {
|
||||
int tab_w = win->content_w / TAB_COUNT;
|
||||
int tab = mx / tab_w;
|
||||
if (tab >= 0 && tab < TAB_COUNT) {
|
||||
st->active_tab = tab;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Content area (offset by TAB_BAR_H)
|
||||
int cy = my - TAB_BAR_H;
|
||||
DesktopSettings& s = st->desktop->settings;
|
||||
|
||||
if (st->active_tab == 0) {
|
||||
// Appearance tab
|
||||
int x = 16;
|
||||
int sfh = system_font_height();
|
||||
int line_h = sfh + 10;
|
||||
int y = 12;
|
||||
|
||||
// "Background" label
|
||||
y += line_h;
|
||||
|
||||
// Radio: Gradient
|
||||
if (mx >= x && mx < x + 100 && cy >= y && cy < y + 16) {
|
||||
s.bg_gradient = true;
|
||||
return;
|
||||
}
|
||||
// Radio: Solid
|
||||
if (mx >= x + 120 && mx < x + 260 && cy >= y && cy < y + 16) {
|
||||
s.bg_gradient = false;
|
||||
return;
|
||||
}
|
||||
y += line_h + 4;
|
||||
|
||||
int idx;
|
||||
if (s.bg_gradient) {
|
||||
// Top swatches
|
||||
if (swatch_hit(mx, cy, x + 70, y, &idx)) {
|
||||
s.bg_grad_top = bg_palette[idx];
|
||||
return;
|
||||
}
|
||||
y += SWATCH_SIZE + 14;
|
||||
|
||||
// Bottom swatches
|
||||
if (swatch_hit(mx, cy, x + 70, y, &idx)) {
|
||||
s.bg_grad_bottom = bg_palette[idx];
|
||||
return;
|
||||
}
|
||||
y += SWATCH_SIZE + 14;
|
||||
} else {
|
||||
// Solid color swatches
|
||||
if (swatch_hit(mx, cy, x + 70, y, &idx)) {
|
||||
s.bg_solid = bg_palette[idx];
|
||||
return;
|
||||
}
|
||||
y += SWATCH_SIZE + 14;
|
||||
}
|
||||
|
||||
// Separator
|
||||
y += 12;
|
||||
|
||||
// Panel color swatches
|
||||
if (swatch_hit(mx, cy, x + 110, y, &idx)) {
|
||||
s.panel_color = panel_palette[idx];
|
||||
return;
|
||||
}
|
||||
y += SWATCH_SIZE + 14;
|
||||
|
||||
// Separator
|
||||
y += 12;
|
||||
|
||||
// Accent color swatches
|
||||
if (swatch_hit(mx, cy, x + 110, y, &idx)) {
|
||||
s.accent_color = accent_palette[idx];
|
||||
return;
|
||||
}
|
||||
} else if (st->active_tab == 1) {
|
||||
// Display tab
|
||||
int x = 16;
|
||||
int y = 20;
|
||||
int btn_w = 60;
|
||||
int btn_h = 28;
|
||||
int bx = x + 180;
|
||||
|
||||
// Window Shadows: On
|
||||
if (mx >= bx && mx < bx + btn_w && cy >= y && cy < y + btn_h) {
|
||||
s.show_shadows = true;
|
||||
return;
|
||||
}
|
||||
// Window Shadows: Off
|
||||
if (mx >= bx + btn_w + 8 && mx < bx + btn_w * 2 + 8 && cy >= y && cy < y + btn_h) {
|
||||
s.show_shadows = false;
|
||||
return;
|
||||
}
|
||||
y += btn_h + 20 + 16;
|
||||
|
||||
// Clock: 24h
|
||||
if (mx >= bx && mx < bx + btn_w && cy >= y && cy < y + btn_h) {
|
||||
s.clock_24h = true;
|
||||
return;
|
||||
}
|
||||
// Clock: 12h
|
||||
if (mx >= bx + btn_w + 8 && mx < bx + btn_w * 2 + 8 && cy >= y && cy < y + btn_h) {
|
||||
s.clock_24h = false;
|
||||
return;
|
||||
}
|
||||
y += btn_h + 20 + 16;
|
||||
|
||||
// UI Scale buttons
|
||||
int sbw = 68;
|
||||
// Small
|
||||
if (mx >= bx && mx < bx + sbw && cy >= y && cy < y + btn_h) {
|
||||
s.ui_scale = 0;
|
||||
apply_ui_scale(0);
|
||||
return;
|
||||
}
|
||||
// Default
|
||||
if (mx >= bx + sbw + 8 && mx < bx + sbw * 2 + 8 && cy >= y && cy < y + btn_h) {
|
||||
s.ui_scale = 1;
|
||||
apply_ui_scale(1);
|
||||
return;
|
||||
}
|
||||
// Large
|
||||
if (mx >= bx + (sbw + 8) * 2 && mx < bx + sbw * 3 + 16 && cy >= y && cy < y + btn_h) {
|
||||
s.ui_scale = 2;
|
||||
apply_ui_scale(2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Cleanup
|
||||
// ============================================================================
|
||||
|
||||
static void settings_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
zenith::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
@@ -69,20 +487,23 @@ static void about_on_close(Window* win) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// About launcher
|
||||
// Settings launcher
|
||||
// ============================================================================
|
||||
|
||||
void open_settings(DesktopState* ds) {
|
||||
int idx = desktop_create_window(ds, "About", 280, 150, 380, 280);
|
||||
int idx = desktop_create_window(ds, "Settings", 200, 100, 480, 420);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
AboutState* st = (AboutState*)zenith::malloc(sizeof(AboutState));
|
||||
zenith::memset(st, 0, sizeof(AboutState));
|
||||
SettingsState* st = (SettingsState*)zenith::malloc(sizeof(SettingsState));
|
||||
zenith::memset(st, 0, sizeof(SettingsState));
|
||||
st->desktop = ds;
|
||||
st->active_tab = 0;
|
||||
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;
|
||||
win->on_draw = settings_on_draw;
|
||||
win->on_mouse = settings_on_mouse;
|
||||
win->on_close = settings_on_close;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
|
||||
|
||||
// Title
|
||||
c.text(x, y, "System Information", colors::ACCENT);
|
||||
y += FONT_HEIGHT + 12;
|
||||
y += system_font_height() + 12;
|
||||
|
||||
// Separator
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
@@ -52,7 +52,7 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
|
||||
// Max Processes
|
||||
snprintf(line, sizeof(line), "Max PIDs: %d", (int)si->sys_info.maxProcesses);
|
||||
c.text(x, y, line, colors::TEXT_COLOR);
|
||||
y += FONT_HEIGHT + 12;
|
||||
y += system_font_height() + 12;
|
||||
|
||||
// Uptime
|
||||
int up_sec = (int)(si->uptime_ms / 1000);
|
||||
@@ -60,11 +60,11 @@ static void sysinfo_on_draw(Window* win, Framebuffer& fb) {
|
||||
int up_hr = up_min / 60;
|
||||
snprintf(line, sizeof(line), "Uptime: %d:%02d:%02d", up_hr, up_min % 60, up_sec % 60);
|
||||
c.text(x, y, line, colors::TEXT_COLOR);
|
||||
y += FONT_HEIGHT + 12;
|
||||
y += system_font_height() + 12;
|
||||
|
||||
// Network section
|
||||
c.text(x, y, "Network", colors::ACCENT);
|
||||
y += FONT_HEIGHT + 8;
|
||||
y += system_font_height() + 8;
|
||||
|
||||
c.hline(x, y, c.w - 2 * x, colors::BORDER);
|
||||
y += 8;
|
||||
|
||||
@@ -15,6 +15,14 @@ static void terminal_on_draw(Window* win, Framebuffer& fb) {
|
||||
if (!ts) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
|
||||
// Check if window was resized and update terminal grid
|
||||
int new_cols = cr.w / mono_cell_width();
|
||||
int new_rows = cr.h / mono_cell_height();
|
||||
if (new_cols != ts->cols || new_rows != ts->rows) {
|
||||
terminal_resize(ts, new_cols, new_rows);
|
||||
}
|
||||
|
||||
terminal_render(ts, win->content, cr.w, cr.h);
|
||||
}
|
||||
|
||||
@@ -52,8 +60,8 @@ void open_terminal(DesktopState* ds) {
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
Rect cr = win->content_rect();
|
||||
int cols = cr.w / FONT_WIDTH;
|
||||
int rows = cr.h / FONT_HEIGHT;
|
||||
int cols = cr.w / mono_cell_width();
|
||||
int rows = cr.h / mono_cell_height();
|
||||
|
||||
TerminalState* ts = (TerminalState*)zenith::malloc(sizeof(TerminalState));
|
||||
zenith::memset(ts, 0, sizeof(TerminalState));
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
// Text Editor state
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int TE_TOOLBAR_H = 36;
|
||||
static constexpr int TE_PATHBAR_H = 32;
|
||||
static constexpr int TE_STATUS_H = 24;
|
||||
static constexpr int TE_LINE_NUM_W = 48;
|
||||
static constexpr int TE_INIT_CAP = 4096;
|
||||
@@ -33,6 +35,11 @@ struct TextEditorState {
|
||||
char filepath[256];
|
||||
char filename[64];
|
||||
DesktopState* desktop;
|
||||
|
||||
bool show_pathbar;
|
||||
char pathbar_text[256];
|
||||
int pathbar_cursor;
|
||||
int pathbar_len;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -203,7 +210,7 @@ static void te_move_end(TextEditorState* te) {
|
||||
// Scrolling
|
||||
// ============================================================================
|
||||
|
||||
static void te_ensure_cursor_visible(TextEditorState* te, int visible_lines) {
|
||||
static void te_ensure_cursor_visible(TextEditorState* te, int visible_lines, int content_w) {
|
||||
if (te->cursor_line < te->scroll_y) {
|
||||
te->scroll_y = te->cursor_line;
|
||||
}
|
||||
@@ -212,13 +219,14 @@ static void te_ensure_cursor_visible(TextEditorState* te, int visible_lines) {
|
||||
}
|
||||
|
||||
// Horizontal scroll
|
||||
int cursor_px = te->cursor_col * FONT_WIDTH;
|
||||
int view_w = 580 - TE_LINE_NUM_W; // approximate
|
||||
if (cursor_px - te->scroll_x > view_w - FONT_WIDTH * 2) {
|
||||
te->scroll_x = cursor_px - view_w + FONT_WIDTH * 4;
|
||||
int cell_w = mono_cell_width();
|
||||
int cursor_px = te->cursor_col * cell_w;
|
||||
int view_w = content_w - TE_LINE_NUM_W;
|
||||
if (cursor_px - te->scroll_x > view_w - cell_w * 2) {
|
||||
te->scroll_x = cursor_px - view_w + cell_w * 4;
|
||||
}
|
||||
if (cursor_px < te->scroll_x) {
|
||||
te->scroll_x = cursor_px - FONT_WIDTH * 2;
|
||||
te->scroll_x = cursor_px - cell_w * 2;
|
||||
if (te->scroll_x < 0) te->scroll_x = 0;
|
||||
}
|
||||
}
|
||||
@@ -290,21 +298,96 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
int text_area_h = c.h - TE_STATUS_H;
|
||||
int visible_lines = text_area_h / FONT_HEIGHT;
|
||||
int cell_w = mono_cell_width();
|
||||
int cell_h = mono_cell_height();
|
||||
|
||||
te_ensure_cursor_visible(te, visible_lines);
|
||||
// ---- Toolbar (36px) ----
|
||||
c.fill_rect(0, 0, c.w, TE_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
|
||||
// Open button
|
||||
Color btn_bg = Color::from_rgb(0xE8, 0xE8, 0xE8);
|
||||
c.fill_rounded_rect(4, 6, 24, 24, 3, btn_bg);
|
||||
if (te->desktop && te->desktop->icon_folder.pixels)
|
||||
c.icon(8, 10, te->desktop->icon_folder);
|
||||
|
||||
// Save button
|
||||
c.fill_rounded_rect(32, 6, 24, 24, 3, btn_bg);
|
||||
if (te->desktop && te->desktop->icon_save.pixels)
|
||||
c.icon(36, 10, te->desktop->icon_save);
|
||||
|
||||
// Vertical separator
|
||||
c.vline(60, 4, 28, colors::BORDER);
|
||||
|
||||
// Filename + modified flag
|
||||
char toolbar_label[128];
|
||||
if (te->filename[0]) {
|
||||
snprintf(toolbar_label, 128, "%s%s", te->filename, te->modified ? " [modified]" : "");
|
||||
} else {
|
||||
snprintf(toolbar_label, 128, "Untitled%s", te->modified ? " [modified]" : "");
|
||||
}
|
||||
int sfh = system_font_height();
|
||||
c.text(68, (TE_TOOLBAR_H - sfh) / 2, toolbar_label, colors::TEXT_COLOR);
|
||||
|
||||
// Toolbar bottom separator
|
||||
c.hline(0, TE_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// ---- Path bar (conditional, 32px) ----
|
||||
int editor_y_start = TE_TOOLBAR_H;
|
||||
|
||||
if (te->show_pathbar) {
|
||||
int pb_y = TE_TOOLBAR_H;
|
||||
c.fill_rect(0, pb_y, c.w, TE_PATHBAR_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
// Text input box
|
||||
int inp_x = 8;
|
||||
int inp_y = pb_y + 4;
|
||||
int btn_w = 56;
|
||||
int inp_w = c.w - inp_x - btn_w - 12;
|
||||
int inp_h = 24;
|
||||
|
||||
c.fill_rect(inp_x, inp_y, inp_w, inp_h, colors::WHITE);
|
||||
c.rect(inp_x, inp_y, inp_w, inp_h, colors::ACCENT);
|
||||
|
||||
// Path text
|
||||
int text_y = inp_y + (inp_h - sfh) / 2;
|
||||
c.text(inp_x + 4, text_y, te->pathbar_text, colors::TEXT_COLOR);
|
||||
|
||||
// Blinking cursor in path input
|
||||
char prefix[256];
|
||||
int plen = te->pathbar_cursor;
|
||||
if (plen > 255) plen = 255;
|
||||
for (int i = 0; i < plen; i++) prefix[i] = te->pathbar_text[i];
|
||||
prefix[plen] = '\0';
|
||||
int cx = inp_x + 4 + text_width(prefix);
|
||||
c.fill_rect(cx, inp_y + 3, 2, inp_h - 6, colors::ACCENT);
|
||||
|
||||
// Open button
|
||||
int ob_x = inp_x + inp_w + 6;
|
||||
c.button(ob_x, inp_y, btn_w, inp_h, "Open", colors::ACCENT, colors::WHITE, 3);
|
||||
|
||||
// Path bar bottom separator
|
||||
c.hline(0, pb_y + TE_PATHBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
editor_y_start = TE_TOOLBAR_H + TE_PATHBAR_H;
|
||||
}
|
||||
|
||||
// ---- Editor area ----
|
||||
int text_area_h = c.h - editor_y_start - TE_STATUS_H;
|
||||
int visible_lines = text_area_h / cell_h;
|
||||
if (visible_lines < 1) visible_lines = 1;
|
||||
|
||||
te_ensure_cursor_visible(te, visible_lines, c.w);
|
||||
|
||||
// Line number gutter background
|
||||
c.fill_rect(0, 0, TE_LINE_NUM_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
c.fill_rect(0, editor_y_start, TE_LINE_NUM_W, text_area_h, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
// Gutter separator
|
||||
c.vline(TE_LINE_NUM_W, 0, text_area_h, colors::BORDER);
|
||||
c.vline(TE_LINE_NUM_W, editor_y_start, text_area_h, colors::BORDER);
|
||||
|
||||
// Draw lines
|
||||
Color linenum_color = Color::from_rgb(0x99, 0x99, 0x99);
|
||||
Color cursor_line_color = Color::from_rgb(0xFF, 0xFD, 0xE8);
|
||||
uint32_t text_px = colors::TEXT_COLOR.to_pixel();
|
||||
Color text_color = colors::TEXT_COLOR;
|
||||
|
||||
int text_start_x = TE_LINE_NUM_W + 4;
|
||||
|
||||
@@ -312,12 +395,12 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
|
||||
int line = te->scroll_y + vis;
|
||||
if (line >= te->line_count) break;
|
||||
|
||||
int py = vis * FONT_HEIGHT;
|
||||
if (py >= text_area_h) break;
|
||||
int py = editor_y_start + vis * cell_h;
|
||||
if (py >= editor_y_start + text_area_h) break;
|
||||
|
||||
// Cursor line highlighting
|
||||
if (line == te->cursor_line) {
|
||||
int hl_h = gui_min(FONT_HEIGHT, text_area_h - py);
|
||||
int hl_h = gui_min(cell_h, editor_y_start + text_area_h - py);
|
||||
if (hl_h > 0)
|
||||
c.fill_rect(TE_LINE_NUM_W + 1, py, c.w - TE_LINE_NUM_W - 1, hl_h, cursor_line_color);
|
||||
}
|
||||
@@ -325,28 +408,37 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
|
||||
// Line number
|
||||
char num_str[8];
|
||||
snprintf(num_str, 8, "%4d", line + 1);
|
||||
c.text(4, py, num_str, linenum_color);
|
||||
c.text_mono(4, py, num_str, linenum_color);
|
||||
|
||||
// Line text (per-character rendering with horizontal scroll clipping)
|
||||
int line_start = te->line_offsets[line];
|
||||
int line_len = te_line_length(te, line);
|
||||
|
||||
for (int ci = 0; ci < line_len; ci++) {
|
||||
int px = text_start_x + ci * FONT_WIDTH - te->scroll_x;
|
||||
if (px + FONT_WIDTH <= TE_LINE_NUM_W + 1) continue;
|
||||
int px = text_start_x + ci * cell_w - te->scroll_x;
|
||||
if (px + cell_w <= TE_LINE_NUM_W + 1) continue;
|
||||
if (px >= c.w) break;
|
||||
|
||||
char ch = te->buffer[line_start + ci];
|
||||
if (ch >= 32 || ch < 0) {
|
||||
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
|
||||
for (int fy = 0; fy < FONT_HEIGHT && py + fy < text_area_h; fy++) {
|
||||
uint8_t bits = glyph[fy];
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
if (bits & (0x80 >> fx)) {
|
||||
int dx = px + fx;
|
||||
int dy = py + fy;
|
||||
if (dx > TE_LINE_NUM_W && dx < c.w && dy >= 0 && dy < text_area_h)
|
||||
c.pixels[dy * c.w + dx] = text_px;
|
||||
if (fonts::mono && fonts::mono->valid) {
|
||||
GlyphCache* gc = fonts::mono->get_cache(fonts::TERM_SIZE);
|
||||
fonts::mono->draw_char_to_buffer(
|
||||
c.pixels, c.w, c.h, px, py + gc->ascent,
|
||||
ch, text_color, gc);
|
||||
} else {
|
||||
// bitmap fallback
|
||||
uint32_t text_px = text_color.to_pixel();
|
||||
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
|
||||
for (int fy = 0; fy < FONT_HEIGHT && py + fy < editor_y_start + text_area_h; fy++) {
|
||||
uint8_t bits = glyph[fy];
|
||||
for (int fx = 0; fx < FONT_WIDTH; fx++) {
|
||||
if (bits & (0x80 >> fx)) {
|
||||
int dx = px + fx;
|
||||
int dy = py + fy;
|
||||
if (dx > TE_LINE_NUM_W && dx < c.w && dy >= 0 && dy < c.h)
|
||||
c.pixels[dy * c.w + dx] = text_px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,9 +447,9 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
|
||||
|
||||
// Draw cursor
|
||||
if (line == te->cursor_line) {
|
||||
int cx = text_start_x + te->cursor_col * FONT_WIDTH - te->scroll_x;
|
||||
int cx = text_start_x + te->cursor_col * cell_w - te->scroll_x;
|
||||
if (cx > TE_LINE_NUM_W && cx + 2 <= c.w) {
|
||||
int cur_h = gui_min(FONT_HEIGHT, text_area_h - py);
|
||||
int cur_h = gui_min(cell_h, editor_y_start + text_area_h - py);
|
||||
if (cur_h > 0)
|
||||
c.fill_rect(cx, py, 2, cur_h, colors::ACCENT);
|
||||
}
|
||||
@@ -368,20 +460,21 @@ static void texteditor_on_draw(Window* win, Framebuffer& fb) {
|
||||
int status_y = c.h - TE_STATUS_H;
|
||||
c.fill_rect(0, status_y, c.w, TE_STATUS_H, Color::from_rgb(0x2B, 0x3E, 0x50));
|
||||
|
||||
// Filename + modified flag
|
||||
// Cursor position (right side)
|
||||
char status_right[32];
|
||||
snprintf(status_right, 32, "Ln %d, Col %d ", te->cursor_line + 1, te->cursor_col + 1);
|
||||
int sr_w = text_width(status_right);
|
||||
int status_text_y = status_y + (TE_STATUS_H - sfh) / 2;
|
||||
c.text(c.w - sr_w - 4, status_text_y, status_right, colors::PANEL_TEXT);
|
||||
|
||||
// Filename + modified flag (left side)
|
||||
char status_left[128];
|
||||
if (te->filename[0]) {
|
||||
snprintf(status_left, 128, " %s%s", te->filename, te->modified ? " [modified]" : "");
|
||||
} else {
|
||||
snprintf(status_left, 128, " Untitled%s", te->modified ? " [modified]" : "");
|
||||
}
|
||||
c.text(4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_left, colors::PANEL_TEXT);
|
||||
|
||||
// Cursor position (right side)
|
||||
char status_right[32];
|
||||
snprintf(status_right, 32, "Ln %d, Col %d ", te->cursor_line + 1, te->cursor_col + 1);
|
||||
int sr_w = zenith::slen(status_right) * FONT_WIDTH;
|
||||
c.text(c.w - sr_w - 4, status_y + (TE_STATUS_H - FONT_HEIGHT) / 2, status_right, colors::PANEL_TEXT);
|
||||
c.text(4, status_text_y, status_left, colors::PANEL_TEXT);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -395,15 +488,62 @@ static void texteditor_on_mouse(Window* win, MouseEvent& ev) {
|
||||
Rect cr = win->content_rect();
|
||||
int local_x = ev.x - cr.x;
|
||||
int local_y = ev.y - cr.y;
|
||||
int text_area_h = cr.h - TE_STATUS_H;
|
||||
|
||||
if (ev.left_pressed() && local_y < text_area_h && local_x > TE_LINE_NUM_W) {
|
||||
// Click to position cursor
|
||||
int clicked_line = te->scroll_y + local_y / FONT_HEIGHT;
|
||||
int cell_w = mono_cell_width();
|
||||
int cell_h = mono_cell_height();
|
||||
|
||||
int editor_y_start = TE_TOOLBAR_H + (te->show_pathbar ? TE_PATHBAR_H : 0);
|
||||
int text_area_h = cr.h - editor_y_start - TE_STATUS_H;
|
||||
|
||||
// ---- Toolbar clicks ----
|
||||
if (ev.left_pressed() && local_y < TE_TOOLBAR_H) {
|
||||
// Open button
|
||||
if (local_x >= 4 && local_x < 28 && local_y >= 6 && local_y < 30) {
|
||||
te->show_pathbar = !te->show_pathbar;
|
||||
if (te->show_pathbar) {
|
||||
// Pre-fill with current filepath
|
||||
zenith::strncpy(te->pathbar_text, te->filepath, 255);
|
||||
te->pathbar_len = zenith::slen(te->pathbar_text);
|
||||
te->pathbar_cursor = te->pathbar_len;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Save button
|
||||
if (local_x >= 32 && local_x < 56 && local_y >= 6 && local_y < 30) {
|
||||
te_save_file(te);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Path bar clicks ----
|
||||
if (te->show_pathbar && local_y >= TE_TOOLBAR_H && local_y < TE_TOOLBAR_H + TE_PATHBAR_H) {
|
||||
if (ev.left_pressed()) {
|
||||
// Check "Open" button region
|
||||
int btn_w = 56;
|
||||
int inp_w = cr.w - 8 - btn_w - 12;
|
||||
int ob_x = 8 + inp_w + 6;
|
||||
if (local_x >= ob_x && local_x < ob_x + btn_w) {
|
||||
if (te->pathbar_text[0]) {
|
||||
te_load_file(te, te->pathbar_text);
|
||||
// Update window title
|
||||
char title[64];
|
||||
snprintf(title, 64, "%s - Editor", te->filename);
|
||||
zenith::strncpy(win->title, title, 63);
|
||||
te->show_pathbar = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Editor area clicks ----
|
||||
if (ev.left_pressed() && local_y >= editor_y_start && local_y < editor_y_start + text_area_h && local_x > TE_LINE_NUM_W) {
|
||||
int clicked_line = te->scroll_y + (local_y - editor_y_start) / cell_h;
|
||||
if (clicked_line >= te->line_count) clicked_line = te->line_count - 1;
|
||||
if (clicked_line < 0) clicked_line = 0;
|
||||
|
||||
int clicked_col = (local_x - TE_LINE_NUM_W - 4 + te->scroll_x + FONT_WIDTH / 2) / FONT_WIDTH;
|
||||
int clicked_col = (local_x - TE_LINE_NUM_W - 4 + te->scroll_x + cell_w / 2) / cell_w;
|
||||
if (clicked_col < 0) clicked_col = 0;
|
||||
int line_len = te_line_length(te, clicked_line);
|
||||
if (clicked_col > line_len) clicked_col = line_len;
|
||||
@@ -412,11 +552,11 @@ static void texteditor_on_mouse(Window* win, MouseEvent& ev) {
|
||||
te_update_cursor_pos(te);
|
||||
}
|
||||
|
||||
// Scroll
|
||||
if (ev.scroll != 0 && local_y < text_area_h) {
|
||||
// ---- Scroll ----
|
||||
if (ev.scroll != 0 && local_y >= editor_y_start && local_y < editor_y_start + text_area_h) {
|
||||
te->scroll_y -= ev.scroll * 3;
|
||||
if (te->scroll_y < 0) te->scroll_y = 0;
|
||||
int max_scroll = te->line_count - (text_area_h / FONT_HEIGHT) + 1;
|
||||
int max_scroll = te->line_count - (text_area_h / cell_h) + 1;
|
||||
if (max_scroll < 0) max_scroll = 0;
|
||||
if (te->scroll_y > max_scroll) te->scroll_y = max_scroll;
|
||||
}
|
||||
@@ -430,12 +570,71 @@ static void texteditor_on_key(Window* win, const Zenith::KeyEvent& key) {
|
||||
TextEditorState* te = (TextEditorState*)win->app_data;
|
||||
if (!te || !key.pressed) return;
|
||||
|
||||
// ---- Path bar input mode ----
|
||||
if (te->show_pathbar) {
|
||||
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||
if (te->pathbar_text[0]) {
|
||||
te_load_file(te, te->pathbar_text);
|
||||
char title[64];
|
||||
snprintf(title, 64, "%s - Editor", te->filename);
|
||||
zenith::strncpy(win->title, title, 63);
|
||||
te->show_pathbar = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x01) { // Escape
|
||||
te->show_pathbar = false;
|
||||
return;
|
||||
}
|
||||
if (key.ascii == '\b' || key.scancode == 0x0E) {
|
||||
if (te->pathbar_cursor > 0) {
|
||||
for (int i = te->pathbar_cursor - 1; i < te->pathbar_len - 1; i++)
|
||||
te->pathbar_text[i] = te->pathbar_text[i + 1];
|
||||
te->pathbar_len--;
|
||||
te->pathbar_cursor--;
|
||||
te->pathbar_text[te->pathbar_len] = '\0';
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4B) { // Left
|
||||
if (te->pathbar_cursor > 0) te->pathbar_cursor--;
|
||||
return;
|
||||
}
|
||||
if (key.scancode == 0x4D) { // Right
|
||||
if (te->pathbar_cursor < te->pathbar_len) te->pathbar_cursor++;
|
||||
return;
|
||||
}
|
||||
if (key.ascii >= 32 && key.ascii < 127 && te->pathbar_len < 254) {
|
||||
for (int i = te->pathbar_len; i > te->pathbar_cursor; i--)
|
||||
te->pathbar_text[i] = te->pathbar_text[i - 1];
|
||||
te->pathbar_text[te->pathbar_cursor] = key.ascii;
|
||||
te->pathbar_cursor++;
|
||||
te->pathbar_len++;
|
||||
te->pathbar_text[te->pathbar_len] = '\0';
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// ---- Normal editor mode ----
|
||||
|
||||
// Ctrl+S: save
|
||||
if (key.ctrl && (key.ascii == 's' || key.ascii == 'S')) {
|
||||
te_save_file(te);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+O: open
|
||||
if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O')) {
|
||||
te->show_pathbar = !te->show_pathbar;
|
||||
if (te->show_pathbar) {
|
||||
zenith::strncpy(te->pathbar_text, te->filepath, 255);
|
||||
te->pathbar_len = zenith::slen(te->pathbar_text);
|
||||
te->pathbar_cursor = te->pathbar_len;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrow keys
|
||||
if (key.scancode == 0x48) { te_move_up(te); return; }
|
||||
if (key.scancode == 0x50) { te_move_down(te); return; }
|
||||
@@ -503,6 +702,10 @@ void open_texteditor(DesktopState* ds) {
|
||||
te->buf_len = 0;
|
||||
te->modified = false;
|
||||
te->desktop = ds;
|
||||
te->show_pathbar = false;
|
||||
te->pathbar_text[0] = '\0';
|
||||
te->pathbar_cursor = 0;
|
||||
te->pathbar_len = 0;
|
||||
|
||||
te_recompute_lines(te);
|
||||
te_update_cursor_pos(te);
|
||||
@@ -536,6 +739,10 @@ void open_texteditor_with_file(DesktopState* ds, const char* path) {
|
||||
te->buf_len = 0;
|
||||
te->modified = false;
|
||||
te->desktop = ds;
|
||||
te->show_pathbar = false;
|
||||
te->pathbar_text[0] = '\0';
|
||||
te->pathbar_cursor = 0;
|
||||
te->pathbar_len = 0;
|
||||
|
||||
// Initialize line index for empty document first (ensures line_offsets
|
||||
// is non-null even if te_load_file fails to open the file)
|
||||
|
||||
@@ -168,7 +168,8 @@ static void wiki_build_display(WikiState* ws, const char* title,
|
||||
ws->lineCount = 0;
|
||||
ws->scrollY = 0;
|
||||
|
||||
int maxChars = (contentW - 24 - WIKI_SCROLLBAR_W) / FONT_WIDTH;
|
||||
int char_w = mono_cell_width();
|
||||
int maxChars = (contentW - 24 - WIKI_SCROLLBAR_W) / char_w;
|
||||
if (maxChars < 20) maxChars = 20;
|
||||
|
||||
Color accent_c = colors::ACCENT;
|
||||
@@ -301,7 +302,7 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) {
|
||||
c.rect(tb_x, tb_y, tb_w, tb_h, colors::BORDER);
|
||||
|
||||
// Search text
|
||||
c.text(tb_x + 4, tb_y + (tb_h - FONT_HEIGHT) / 2, ws->searchQuery, colors::TEXT_COLOR);
|
||||
c.text(tb_x + 4, tb_y + (tb_h - system_font_height()) / 2, ws->searchQuery, colors::TEXT_COLOR);
|
||||
|
||||
// ---- Search button ----
|
||||
int btn_x = tb_x + tb_w + 6;
|
||||
@@ -313,7 +314,8 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) {
|
||||
// ---- Content area ----
|
||||
int content_y = WIKI_TOOLBAR_H + 1;
|
||||
int content_h = c.h - content_y;
|
||||
int visibleLines = content_h / (FONT_HEIGHT + 4);
|
||||
int wiki_sfh = system_font_height();
|
||||
int visibleLines = content_h / (wiki_sfh + 4);
|
||||
if (visibleLines < 1) visibleLines = 1;
|
||||
|
||||
if (ws->mode == WikiState::FETCHING) {
|
||||
@@ -326,8 +328,8 @@ static void wiki_on_draw(Window* win, Framebuffer& fb) {
|
||||
Color::from_rgb(0x88, 0x88, 0x88));
|
||||
} else if (ws->mode == WikiState::DONE && ws->lineCount > 0) {
|
||||
int y = content_y + 8;
|
||||
int lineH = FONT_HEIGHT + 4;
|
||||
for (int i = ws->scrollY; i < ws->lineCount && y + FONT_HEIGHT < c.h; i++) {
|
||||
int lineH = wiki_sfh + 4;
|
||||
for (int i = ws->scrollY; i < ws->lineCount && y + wiki_sfh < c.h; i++) {
|
||||
WikiDisplayLine* dl = &ws->lines[i];
|
||||
if (dl->text[0] != '\0') {
|
||||
c.text(12, y, dl->text, dl->color);
|
||||
@@ -412,7 +414,7 @@ static void wiki_on_mouse(Window* win, MouseEvent& ev) {
|
||||
if (ev.scroll != 0 && ws->mode == WikiState::DONE && ws->lineCount > 0) {
|
||||
int ch = cr.h;
|
||||
int content_h = ch - WIKI_TOOLBAR_H - 1;
|
||||
int visibleLines = content_h / (FONT_HEIGHT + 4);
|
||||
int visibleLines = content_h / (system_font_height() + 4);
|
||||
int maxScroll = ws->lineCount - visibleLines;
|
||||
if (maxScroll < 0) maxScroll = 0;
|
||||
|
||||
@@ -443,7 +445,7 @@ static void wiki_on_key(Window* win, const Zenith::KeyEvent& key) {
|
||||
}
|
||||
}
|
||||
int content_h = cr.h - WIKI_TOOLBAR_H - 1;
|
||||
int visibleLines = content_h / (FONT_HEIGHT + 4);
|
||||
int visibleLines = content_h / (system_font_height() + 4);
|
||||
if (visibleLines < 1) visibleLines = 1;
|
||||
int maxScroll = ws->lineCount - visibleLines;
|
||||
if (maxScroll < 0) maxScroll = 0;
|
||||
|
||||
@@ -167,5 +167,10 @@ void open_texteditor(DesktopState* ds);
|
||||
void open_texteditor_with_file(DesktopState* ds, const char* path);
|
||||
void open_klog(DesktopState* ds);
|
||||
void open_wiki(DesktopState* ds);
|
||||
void open_procmgr(DesktopState* ds);
|
||||
void open_mandelbrot(DesktopState* ds);
|
||||
void open_devexplorer(DesktopState* ds);
|
||||
void open_settings(DesktopState* ds);
|
||||
void open_doom(DesktopState* ds);
|
||||
void open_reboot_dialog(DesktopState* ds);
|
||||
void desktop_poll_external_windows(DesktopState* ds);
|
||||
|
||||
+281
-69
@@ -23,6 +23,9 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
ds->fb.clear(colors::DESKTOP_BG);
|
||||
ds->fb.flip();
|
||||
|
||||
// Load TrueType fonts
|
||||
fonts::init();
|
||||
|
||||
ds->window_count = 0;
|
||||
ds->focused_window = -1;
|
||||
ds->prev_buttons = 0;
|
||||
@@ -58,6 +61,22 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
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->icon_doom = svg_load("0:/icons/doom.svg", 20, 20, defColor);
|
||||
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
||||
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
|
||||
ds->icon_devexplorer = svg_load("0:/icons/hardware.svg", 20, 20, defColor);
|
||||
|
||||
// Settings defaults
|
||||
ds->settings.bg_gradient = true;
|
||||
ds->settings.bg_grad_top = Color::from_rgb(0xD0, 0xD8, 0xE8);
|
||||
ds->settings.bg_grad_bottom = Color::from_rgb(0xA0, 0xA8, 0xB8);
|
||||
ds->settings.bg_solid = Color::from_rgb(0xD0, 0xD8, 0xE8);
|
||||
ds->settings.panel_color = colors::PANEL_BG;
|
||||
ds->settings.accent_color = colors::ACCENT;
|
||||
ds->settings.show_shadows = true;
|
||||
ds->settings.clock_24h = true;
|
||||
ds->settings.ui_scale = 1;
|
||||
|
||||
ds->ctx_menu_open = false;
|
||||
ds->ctx_menu_x = 0;
|
||||
ds->ctx_menu_y = 0;
|
||||
@@ -100,6 +119,8 @@ int gui::desktop_create_window(DesktopState* ds, const char* title, int x, int y
|
||||
win->on_close = nullptr;
|
||||
win->on_poll = nullptr;
|
||||
win->app_data = nullptr;
|
||||
win->external = false;
|
||||
win->ext_win_id = -1;
|
||||
|
||||
// Unfocus previous window
|
||||
if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
|
||||
@@ -115,10 +136,19 @@ void gui::desktop_close_window(DesktopState* ds, int idx) {
|
||||
if (idx < 0 || idx >= ds->window_count) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
|
||||
// For external windows, send a close event instead of freeing the buffer
|
||||
if (win->external) {
|
||||
Zenith::WinEvent ev;
|
||||
zenith::memset(&ev, 0, sizeof(ev));
|
||||
ev.type = 3; // close
|
||||
zenith::win_sendevent(win->ext_win_id, &ev);
|
||||
}
|
||||
|
||||
if (win->on_close) win->on_close(win);
|
||||
|
||||
// Free content buffer
|
||||
if (win->content) {
|
||||
// Free content buffer (skip for external windows — shared memory)
|
||||
if (win->content && !win->external) {
|
||||
zenith::free(win->content);
|
||||
win->content = nullptr;
|
||||
}
|
||||
@@ -180,7 +210,8 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) {
|
||||
int h = win->frame.h;
|
||||
|
||||
// Draw shadow
|
||||
draw_shadow(fb, x, y, w, h, SHADOW_SIZE, colors::SHADOW);
|
||||
if (ds->settings.show_shadows)
|
||||
draw_shadow(fb, x, y, w, h, SHADOW_SIZE, colors::SHADOW);
|
||||
|
||||
// Draw window body
|
||||
fb.fill_rect(x, y, w, h, colors::WINDOW_BG);
|
||||
@@ -205,7 +236,7 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) {
|
||||
|
||||
// Draw title text centered in titlebar (after buttons)
|
||||
int title_x = x + 12 + 44 + BTN_RADIUS * 2 + 12; // after buttons
|
||||
int title_y = y + (TITLEBAR_HEIGHT - FONT_HEIGHT) / 2;
|
||||
int title_y = y + (TITLEBAR_HEIGHT - system_font_height()) / 2;
|
||||
int title_w = text_width(win->title);
|
||||
// Center in remaining space
|
||||
int remaining_w = w - (title_x - x) - 12;
|
||||
@@ -222,9 +253,31 @@ void gui::desktop_draw_window(DesktopState* ds, int idx) {
|
||||
// Blit content buffer to framebuffer (clip to actual buffer size during resize)
|
||||
Rect cr = win->content_rect();
|
||||
if (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);
|
||||
if (win->external && (cr.w != win->content_w || cr.h != win->content_h)) {
|
||||
// Nearest-neighbor scale for external windows (fixed-size shared buffer)
|
||||
int src_w = win->content_w;
|
||||
int src_h = win->content_h;
|
||||
int dst_w = cr.w;
|
||||
int dst_h = cr.h;
|
||||
uint32_t* buf = fb.buffer();
|
||||
int pitch = fb.pitch();
|
||||
for (int y = 0; y < dst_h; y++) {
|
||||
int dy = cr.y + y;
|
||||
if (dy < 0 || dy >= fb.height()) continue;
|
||||
int sy = y * src_h / dst_h;
|
||||
uint32_t* dst_row = (uint32_t*)((uint8_t*)buf + dy * pitch);
|
||||
uint32_t* src_row = win->content + sy * src_w;
|
||||
for (int x = 0; x < dst_w; x++) {
|
||||
int dx = cr.x + x;
|
||||
if (dx < 0 || dx >= fb.width()) continue;
|
||||
dst_row[dx] = src_row[x * src_w / dst_w];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,11 +286,12 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
int sw = ds->screen_w;
|
||||
|
||||
// Panel gradient background (slightly lighter at top)
|
||||
Color pc = ds->settings.panel_color;
|
||||
for (int y = 0; y < PANEL_HEIGHT; y++) {
|
||||
int t = y * 255 / PANEL_HEIGHT;
|
||||
uint8_t r = colors::PANEL_BG.r + (10 - t * 10 / 255);
|
||||
uint8_t g = colors::PANEL_BG.g + (10 - t * 10 / 255);
|
||||
uint8_t b = colors::PANEL_BG.b + (10 - t * 10 / 255);
|
||||
uint8_t r = pc.r + (10 - t * 10 / 255);
|
||||
uint8_t g = pc.g + (10 - t * 10 / 255);
|
||||
uint8_t b = pc.b + (10 - t * 10 / 255);
|
||||
fb.fill_rect(0, y, sw, 1, Color::from_rgb(r, g, b));
|
||||
}
|
||||
|
||||
@@ -284,7 +338,7 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
|
||||
// Active window accent underline bar
|
||||
if (i == ds->focused_window) {
|
||||
fb.fill_rect(indicator_x + 4, 26, iw - 8, 2, colors::ACCENT);
|
||||
fb.fill_rect(indicator_x + 4, 26, iw - 8, 2, ds->settings.accent_color);
|
||||
}
|
||||
|
||||
// Truncate title if too long
|
||||
@@ -292,7 +346,7 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
zenith::strncpy(short_title, win->title, 18);
|
||||
|
||||
int tx = indicator_x + pad;
|
||||
int ty = 4 + (24 - FONT_HEIGHT) / 2;
|
||||
int ty = 4 + (24 - system_font_height()) / 2;
|
||||
draw_text(fb, tx, ty, short_title, colors::PANEL_TEXT);
|
||||
|
||||
indicator_x += iw + 4;
|
||||
@@ -302,11 +356,18 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
Zenith::DateTime dt;
|
||||
zenith::gettime(&dt);
|
||||
|
||||
char clock_str[8];
|
||||
snprintf(clock_str, sizeof(clock_str), "%02d:%02d", (int)dt.Hour, (int)dt.Minute);
|
||||
char clock_str[12];
|
||||
if (ds->settings.clock_24h) {
|
||||
snprintf(clock_str, sizeof(clock_str), "%02d:%02d", (int)dt.Hour, (int)dt.Minute);
|
||||
} else {
|
||||
int h12 = (int)dt.Hour % 12;
|
||||
if (h12 == 0) h12 = 12;
|
||||
const char* ampm = dt.Hour < 12 ? "AM" : "PM";
|
||||
snprintf(clock_str, sizeof(clock_str), "%d:%02d %s", h12, (int)dt.Minute, ampm);
|
||||
}
|
||||
int clock_w = text_width(clock_str);
|
||||
int clock_x = sw - clock_w - 12;
|
||||
int clock_y = (PANEL_HEIGHT - FONT_HEIGHT) / 2;
|
||||
int clock_y = (PANEL_HEIGHT - system_font_height()) / 2;
|
||||
draw_text(fb, clock_x, clock_y, clock_str, colors::PANEL_TEXT);
|
||||
|
||||
// Date before clock
|
||||
@@ -314,7 +375,7 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
int month_idx = dt.Month > 0 && dt.Month <= 12 ? dt.Month - 1 : 0;
|
||||
snprintf(date_str, sizeof(date_str), "%s %d", month_names[month_idx], (int)dt.Day);
|
||||
int date_w = text_width(date_str);
|
||||
int date_x = clock_x - date_w - 16;
|
||||
int date_x = clock_x - date_w - 10;
|
||||
draw_text(fb, date_x, clock_y, date_str, colors::PANEL_TEXT);
|
||||
|
||||
// Network icon (to the left of the date)
|
||||
@@ -349,7 +410,7 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
// App Menu (5 items with separator and rounded corners)
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MENU_ITEM_COUNT = 9;
|
||||
static constexpr int MENU_ITEM_COUNT = 13;
|
||||
static constexpr int MENU_W = 220;
|
||||
static constexpr int MENU_ITEM_H = 40;
|
||||
|
||||
@@ -379,8 +440,12 @@ static void desktop_draw_app_menu(DesktopState* ds) {
|
||||
{ "Calculator", &ds->icon_calculator },
|
||||
{ "Text Editor", &ds->icon_texteditor },
|
||||
{ "Kernel Log", &ds->icon_terminal },
|
||||
{ "Processes", &ds->icon_procmgr },
|
||||
{ "Mandelbrot", &ds->icon_mandelbrot },
|
||||
{ "Devices", &ds->icon_devexplorer },
|
||||
{ "Wikipedia", &ds->icon_wikipedia },
|
||||
{ "About", &ds->icon_settings },
|
||||
{ "DOOM", &ds->icon_doom },
|
||||
{ "Settings", &ds->icon_settings },
|
||||
{ "Reboot", &ds->icon_reboot },
|
||||
};
|
||||
|
||||
@@ -391,7 +456,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
|
||||
int iy = menu_y + 4 + i * MENU_ITEM_H;
|
||||
|
||||
// Thin separator lines before utility apps and before Settings
|
||||
if (i == 3 || i == 7) {
|
||||
if (i == 3 || i == 11) {
|
||||
int sep_y = iy - 1;
|
||||
for (int sx = menu_x + 8; sx < menu_x + MENU_W - 8; sx++)
|
||||
fb.put_pixel(sx, sep_y, colors::BORDER);
|
||||
@@ -414,7 +479,7 @@ static void desktop_draw_app_menu(DesktopState* ds) {
|
||||
|
||||
// Label
|
||||
int tx = icon_x + 28;
|
||||
int ty = item_rect.y + (MENU_ITEM_H - FONT_HEIGHT) / 2;
|
||||
int ty = item_rect.y + (MENU_ITEM_H - system_font_height()) / 2;
|
||||
draw_text(fb, tx, ty, items[i].label, colors::TEXT_COLOR);
|
||||
}
|
||||
}
|
||||
@@ -434,7 +499,7 @@ static void desktop_draw_net_popup(DesktopState* ds) {
|
||||
|
||||
int tx = popup_x + 12;
|
||||
int ty = popup_y + 10;
|
||||
int line_h = FONT_HEIGHT + 6;
|
||||
int line_h = system_font_height() + 6;
|
||||
char line[64];
|
||||
|
||||
Zenith::NetCfg& nc = ds->cached_net_cfg;
|
||||
@@ -645,14 +710,19 @@ void gui::desktop_compose(DesktopState* ds) {
|
||||
uint32_t* row = (uint32_t*)((uint8_t*)buf + y * pitch);
|
||||
if (y < grad_start) {
|
||||
// Panel area - will be overwritten by panel drawing
|
||||
uint32_t px = colors::PANEL_BG.to_pixel();
|
||||
uint32_t px = ds->settings.panel_color.to_pixel();
|
||||
for (int x = 0; x < sw; x++) row[x] = px;
|
||||
} else if (ds->settings.bg_gradient) {
|
||||
int t = y - grad_start;
|
||||
Color top = ds->settings.bg_grad_top;
|
||||
Color bot = ds->settings.bg_grad_bottom;
|
||||
uint8_t r = top.r - (top.r - bot.r) * t / grad_range;
|
||||
uint8_t g = top.g - (top.g - bot.g) * t / grad_range;
|
||||
uint8_t b = top.b - (top.b - bot.b) * t / grad_range;
|
||||
uint32_t px = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
for (int x = 0; x < sw; x++) row[x] = px;
|
||||
} else {
|
||||
int t = y - grad_start;
|
||||
uint8_t r = 0xD0 - (0xD0 - 0xA0) * t / grad_range;
|
||||
uint8_t g = 0xD8 - (0xD8 - 0xA8) * t / grad_range;
|
||||
uint8_t b = 0xE8 - (0xE8 - 0xB8) * t / grad_range;
|
||||
uint32_t px = 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
uint32_t px = ds->settings.bg_solid.to_pixel();
|
||||
for (int x = 0; x < sw; x++) row[x] = px;
|
||||
}
|
||||
}
|
||||
@@ -720,7 +790,7 @@ void gui::desktop_compose(DesktopState* ds) {
|
||||
}
|
||||
|
||||
int tx = icon_x + 28;
|
||||
int ty = item_r.y + (CTX_ITEM_H - FONT_HEIGHT) / 2;
|
||||
int ty = item_r.y + (CTX_ITEM_H - system_font_height()) / 2;
|
||||
draw_text(fb, tx, ty, ctx_items[i].label, colors::TEXT_COLOR);
|
||||
}
|
||||
}
|
||||
@@ -836,25 +906,29 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
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);
|
||||
if (!win->external) {
|
||||
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);
|
||||
if (!win->external) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -903,14 +977,16 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
}
|
||||
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);
|
||||
// Reallocate content buffer if dimensions changed (skip for external)
|
||||
if (!win->external) {
|
||||
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;
|
||||
}
|
||||
@@ -936,9 +1012,13 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
case 3: open_calculator(ds); break;
|
||||
case 4: open_texteditor(ds); break;
|
||||
case 5: open_klog(ds); break;
|
||||
case 6: open_wiki(ds); break;
|
||||
case 7: open_settings(ds); break;
|
||||
case 8: open_reboot_dialog(ds); break;
|
||||
case 6: open_procmgr(ds); break;
|
||||
case 7: open_mandelbrot(ds); break;
|
||||
case 8: open_devexplorer(ds); break;
|
||||
case 9: open_wiki(ds); break;
|
||||
case 10: open_doom(ds); break;
|
||||
case 11: open_settings(ds); break;
|
||||
case 12: open_reboot_dialog(ds); break;
|
||||
}
|
||||
ds->app_menu_open = false;
|
||||
}
|
||||
@@ -1046,13 +1126,16 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
win->frame = {0, PANEL_HEIGHT, ds->screen_w, 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);
|
||||
// Reallocate content buffer for local windows only
|
||||
if (!win->external) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
desktop_raise_window(ds, i);
|
||||
return;
|
||||
@@ -1097,10 +1180,22 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
if (cr.contains(mx, my)) {
|
||||
desktop_raise_window(ds, i);
|
||||
int new_idx = ds->window_count - 1;
|
||||
if (ds->windows[new_idx].on_mouse) {
|
||||
Window* raised = &ds->windows[new_idx];
|
||||
if (raised->external) {
|
||||
// Forward mouse event to external window
|
||||
Zenith::WinEvent wev;
|
||||
zenith::memset(&wev, 0, sizeof(wev));
|
||||
wev.type = 1; // mouse
|
||||
wev.mouse.x = mx - cr.x;
|
||||
wev.mouse.y = my - cr.y;
|
||||
wev.mouse.scroll = ev.scroll;
|
||||
wev.mouse.buttons = buttons;
|
||||
wev.mouse.prev_buttons = prev;
|
||||
zenith::win_sendevent(raised->ext_win_id, &wev);
|
||||
} else if (raised->on_mouse) {
|
||||
ev.x = mx;
|
||||
ev.y = my;
|
||||
ds->windows[new_idx].on_mouse(&ds->windows[new_idx], ev);
|
||||
raised->on_mouse(raised, ev);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1120,8 +1215,20 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
if (ev.scroll != 0 && ds->focused_window >= 0) {
|
||||
Window* win = &ds->windows[ds->focused_window];
|
||||
Rect cr = win->content_rect();
|
||||
if (cr.contains(mx, my) && win->on_mouse) {
|
||||
win->on_mouse(win, ev);
|
||||
if (cr.contains(mx, my)) {
|
||||
if (win->external) {
|
||||
Zenith::WinEvent wev;
|
||||
zenith::memset(&wev, 0, sizeof(wev));
|
||||
wev.type = 1; // mouse
|
||||
wev.mouse.x = mx - cr.x;
|
||||
wev.mouse.y = my - cr.y;
|
||||
wev.mouse.scroll = ev.scroll;
|
||||
wev.mouse.buttons = buttons;
|
||||
wev.mouse.prev_buttons = prev;
|
||||
zenith::win_sendevent(win->ext_win_id, &wev);
|
||||
} else if (win->on_mouse) {
|
||||
win->on_mouse(win, ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1147,10 +1254,8 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
}
|
||||
|
||||
void gui::desktop_handle_keyboard(DesktopState* ds, const Zenith::KeyEvent& key) {
|
||||
if (!key.pressed) return;
|
||||
|
||||
// Global shortcuts
|
||||
if (key.ctrl && key.alt) {
|
||||
// Global shortcuts (only on key press)
|
||||
if (key.pressed && key.ctrl && key.alt) {
|
||||
if (key.ascii == 't' || key.ascii == 'T') {
|
||||
open_terminal(ds);
|
||||
return;
|
||||
@@ -1175,17 +1280,121 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const Zenith::KeyEvent& key)
|
||||
open_klog(ds);
|
||||
return;
|
||||
}
|
||||
if (key.ascii == 'd' || key.ascii == 'D') {
|
||||
open_doom(ds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch to focused window
|
||||
if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
|
||||
Window* win = &ds->windows[ds->focused_window];
|
||||
if (win->on_key) {
|
||||
if (win->external) {
|
||||
// Forward key event to external window via syscall
|
||||
Zenith::WinEvent ev;
|
||||
zenith::memset(&ev, 0, sizeof(ev));
|
||||
ev.type = 0; // key
|
||||
ev.key = key;
|
||||
zenith::win_sendevent(win->ext_win_id, &ev);
|
||||
} else if (win->on_key) {
|
||||
win->on_key(win, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// External Window Polling
|
||||
// ============================================================================
|
||||
|
||||
void desktop_poll_external_windows(DesktopState* ds) {
|
||||
Zenith::WinInfo extWins[8];
|
||||
int extCount = zenith::win_enumerate(extWins, 8);
|
||||
|
||||
// Check for new external windows and map them
|
||||
for (int e = 0; e < extCount; e++) {
|
||||
int extId = extWins[e].id;
|
||||
|
||||
// Check if we already have this window
|
||||
bool found = false;
|
||||
for (int i = 0; i < ds->window_count; i++) {
|
||||
if (ds->windows[i].external && ds->windows[i].ext_win_id == extId) {
|
||||
found = true;
|
||||
// Update dirty flag
|
||||
if (extWins[e].dirty) {
|
||||
ds->windows[i].dirty = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found && ds->window_count < MAX_WINDOWS) {
|
||||
// Map the pixel buffer into our address space
|
||||
uint64_t va = zenith::win_map(extId);
|
||||
if (va == 0) continue;
|
||||
|
||||
int idx = ds->window_count;
|
||||
Window* win = &ds->windows[idx];
|
||||
zenith::memset(win, 0, sizeof(Window));
|
||||
|
||||
zenith::strncpy(win->title, extWins[e].title, MAX_TITLE_LEN);
|
||||
int w = extWins[e].width;
|
||||
int h = extWins[e].height;
|
||||
// Position the window centered-ish
|
||||
int wx = (ds->screen_w - w - 2 * BORDER_WIDTH) / 2 + idx * 30;
|
||||
int wy = PANEL_HEIGHT + 20 + idx * 30;
|
||||
win->frame = {wx, wy, w + 2 * BORDER_WIDTH, h + TITLEBAR_HEIGHT + BORDER_WIDTH};
|
||||
win->state = WIN_NORMAL;
|
||||
win->z_order = idx;
|
||||
win->focused = true;
|
||||
win->dirty = true;
|
||||
win->dragging = false;
|
||||
win->resizing = false;
|
||||
win->saved_frame = win->frame;
|
||||
|
||||
// Point content to the shared pixel buffer
|
||||
win->content = (uint32_t*)va;
|
||||
win->content_w = w;
|
||||
win->content_h = h;
|
||||
|
||||
win->on_draw = nullptr;
|
||||
win->on_mouse = nullptr;
|
||||
win->on_key = nullptr;
|
||||
win->on_close = nullptr;
|
||||
win->on_poll = nullptr;
|
||||
win->app_data = nullptr;
|
||||
win->external = true;
|
||||
win->ext_win_id = extId;
|
||||
|
||||
// Unfocus previous window
|
||||
if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
|
||||
ds->windows[ds->focused_window].focused = false;
|
||||
}
|
||||
ds->focused_window = idx;
|
||||
ds->window_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for removed external windows (process exited)
|
||||
for (int i = ds->window_count - 1; i >= 0; i--) {
|
||||
if (!ds->windows[i].external) continue;
|
||||
|
||||
int extId = ds->windows[i].ext_win_id;
|
||||
bool stillExists = false;
|
||||
for (int e = 0; e < extCount; e++) {
|
||||
if (extWins[e].id == extId) {
|
||||
stillExists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stillExists) {
|
||||
// Window gone — remove without freeing content (shared memory)
|
||||
ds->windows[i].content = nullptr; // prevent free
|
||||
gui::desktop_close_window(ds, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void gui::desktop_run(DesktopState* ds) {
|
||||
for (;;) {
|
||||
// Poll mouse state
|
||||
@@ -1199,6 +1408,9 @@ void gui::desktop_run(DesktopState* ds) {
|
||||
desktop_handle_keyboard(ds, key);
|
||||
}
|
||||
|
||||
// Poll external windows (discover new, remove dead, update dirty)
|
||||
desktop_poll_external_windows(ds);
|
||||
|
||||
// Poll windows that have a poll callback
|
||||
for (int i = 0; i < ds->window_count; i++) {
|
||||
Window* win = &ds->windows[i];
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* stb_truetype_impl.cpp
|
||||
* Single compilation unit for stb_truetype in ZenithOS freestanding environment
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <zenith/heap.h>
|
||||
#include <zenith/string.h>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
// Override all stb_truetype dependencies before including the implementation
|
||||
|
||||
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||
#define STBTT_cos(x) stb_cos(x)
|
||||
#define STBTT_acos(x) stb_acos(x)
|
||||
#define STBTT_fabs(x) stb_fabs(x)
|
||||
|
||||
#define STBTT_malloc(x,u) ((void)(u), zenith::malloc(x))
|
||||
#define STBTT_free(x,u) ((void)(u), zenith::mfree(x))
|
||||
|
||||
#define STBTT_memcpy(d,s,n) zenith::memcpy(d,s,n)
|
||||
#define STBTT_memset(d,v,n) zenith::memset(d,v,n)
|
||||
|
||||
#define STBTT_strlen(x) zenith::slen(x)
|
||||
|
||||
#define STBTT_assert(x) ((void)(x))
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <gui/stb_truetype.h>
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* doomgeneric_zenith.c
|
||||
* DOOM platform implementation for ZenithOS
|
||||
* DOOM platform implementation for ZenithOS (standalone window server client)
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
@@ -30,41 +30,70 @@ static inline long _zos_syscall1(long nr, long a1) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _zos_syscall2(long nr, long a1, long a2) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"mov %[a2], %%rsi\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) {
|
||||
long ret;
|
||||
__asm__ volatile(
|
||||
"mov %[a1], %%rdi\n\t"
|
||||
"mov %[a2], %%rsi\n\t"
|
||||
"mov %[a3], %%rdx\n\t"
|
||||
"mov %[a4], %%r10\n\t"
|
||||
"syscall"
|
||||
: "=a"(ret)
|
||||
: "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4)
|
||||
: "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Syscall numbers (must match kernel/src/Api/Syscall.hpp) */
|
||||
#define SYS_EXIT 0
|
||||
#define SYS_SLEEP_MS 2
|
||||
#define SYS_PRINT 4
|
||||
#define SYS_GETMILLISECONDS 14
|
||||
#define SYS_ISKEYAVAILABLE 16
|
||||
#define SYS_GETKEY 17
|
||||
#define SYS_FBINFO 21
|
||||
#define SYS_FBMAP 22
|
||||
#define SYS_WINCREATE 54
|
||||
#define SYS_WINDESTROY 55
|
||||
#define SYS_WINPRESENT 56
|
||||
#define SYS_WINPOLL 57
|
||||
|
||||
/* FbInfo struct (must match kernel definition) */
|
||||
struct FbInfo {
|
||||
unsigned long width;
|
||||
unsigned long height;
|
||||
unsigned long pitch;
|
||||
unsigned long bpp;
|
||||
unsigned long userAddr;
|
||||
/* Window server structs (must match Zenith::WinCreateResult and Zenith::WinEvent) */
|
||||
struct WinCreateResult {
|
||||
int id; /* -1 on failure */
|
||||
unsigned _pad;
|
||||
unsigned long pixelVa; /* VA of pixel buffer in caller's address space */
|
||||
};
|
||||
|
||||
/* KeyEvent struct (must match kernel definition) */
|
||||
struct KeyEvent {
|
||||
unsigned char scancode;
|
||||
char ascii;
|
||||
unsigned char pressed;
|
||||
unsigned char shift;
|
||||
unsigned char ctrl;
|
||||
unsigned char alt;
|
||||
struct WinEvent {
|
||||
unsigned char type; /* 0=key, 1=mouse, 2=resize, 3=close */
|
||||
unsigned char _pad[3];
|
||||
union {
|
||||
struct {
|
||||
unsigned char scancode;
|
||||
char ascii;
|
||||
unsigned char pressed;
|
||||
unsigned char shift;
|
||||
unsigned char ctrl;
|
||||
unsigned char alt;
|
||||
} key;
|
||||
struct { int x, y, scroll; unsigned char buttons, prev_buttons; } mouse;
|
||||
struct { int w, h; } resize;
|
||||
};
|
||||
};
|
||||
|
||||
/* ---- Framebuffer state ---- */
|
||||
/* ---- Window state ---- */
|
||||
|
||||
static uint32_t* g_fbPtr = 0;
|
||||
static uint32_t g_fbWidth = 0;
|
||||
static uint32_t g_fbHeight = 0;
|
||||
static uint32_t g_fbPitch = 0; /* bytes per scanline */
|
||||
static int g_winId = -1;
|
||||
static uint32_t* g_pixBuf = 0;
|
||||
|
||||
/* ---- Circular key queue ---- */
|
||||
|
||||
@@ -115,8 +144,8 @@ static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) {
|
||||
case 0x4D: return KEY_RIGHTARROW;
|
||||
case 0x1C: return KEY_ENTER;
|
||||
case 0x01: return KEY_ESCAPE;
|
||||
case 0x39: return ' '; /* Space = use */
|
||||
case 0x1D: return KEY_RCTRL; /* LCtrl = fire */
|
||||
case 0x39: return KEY_USE; /* Space = use */
|
||||
case 0x1D: return KEY_FIRE; /* LCtrl = fire */
|
||||
case 0x2A: return KEY_RSHIFT; /* LShift = run */
|
||||
case 0x36: return KEY_RSHIFT; /* RShift = run */
|
||||
case 0x38: return KEY_RALT; /* Alt = strafe */
|
||||
@@ -146,22 +175,26 @@ static unsigned char scancode_to_doomkey(unsigned char scancode, char ascii) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Poll keyboard and enqueue events ---- */
|
||||
/* ---- Poll window events and enqueue key events ---- */
|
||||
|
||||
static void poll_keyboard(void) {
|
||||
while (_zos_syscall0(SYS_ISKEYAVAILABLE)) {
|
||||
struct KeyEvent evt;
|
||||
_zos_syscall1(SYS_GETKEY, (long)&evt);
|
||||
struct WinEvent evt;
|
||||
while (_zos_syscall2(SYS_WINPOLL, (long)g_winId, (long)&evt) > 0) {
|
||||
if (evt.type == 0) {
|
||||
/* Key event */
|
||||
unsigned char baseSc = evt.key.scancode & 0x7F;
|
||||
|
||||
unsigned char baseSc = evt.scancode & 0x7F; /* strip break bit */
|
||||
char ascii = 0;
|
||||
if (baseSc < 128)
|
||||
ascii = scancode_to_ascii[baseSc];
|
||||
|
||||
char ascii = 0;
|
||||
if (baseSc < 128)
|
||||
ascii = scancode_to_ascii[baseSc];
|
||||
|
||||
unsigned char dk = scancode_to_doomkey(baseSc, ascii);
|
||||
if (dk != 0) {
|
||||
key_queue_push(evt.pressed ? 1 : 0, dk);
|
||||
unsigned char dk = scancode_to_doomkey(baseSc, ascii);
|
||||
if (dk != 0) {
|
||||
key_queue_push(evt.key.pressed ? 1 : 0, dk);
|
||||
}
|
||||
} else if (evt.type == 3) {
|
||||
/* Close event — exit the process */
|
||||
_zos_syscall1(SYS_EXIT, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,38 +202,30 @@ static void poll_keyboard(void) {
|
||||
/* ---- DG platform functions ---- */
|
||||
|
||||
void DG_Init(void) {
|
||||
struct FbInfo info;
|
||||
_zos_syscall1(SYS_FBINFO, (long)&info);
|
||||
struct WinCreateResult result;
|
||||
_zos_syscall4(SYS_WINCREATE, (long)"DOOM", (long)DOOMGENERIC_RESX,
|
||||
(long)DOOMGENERIC_RESY, (long)&result);
|
||||
|
||||
g_fbWidth = (uint32_t)info.width;
|
||||
g_fbHeight = (uint32_t)info.height;
|
||||
g_fbPitch = (uint32_t)info.pitch;
|
||||
if (result.id < 0) {
|
||||
_zos_syscall1(SYS_EXIT, 1);
|
||||
}
|
||||
|
||||
g_fbPtr = (uint32_t*)(unsigned long)_zos_syscall0(SYS_FBMAP);
|
||||
|
||||
printf("DOOM: framebuffer %ux%u pitch=%u mapped at %p\n",
|
||||
g_fbWidth, g_fbHeight, g_fbPitch, (void*)g_fbPtr);
|
||||
g_winId = result.id;
|
||||
g_pixBuf = (uint32_t*)result.pixelVa;
|
||||
}
|
||||
|
||||
void DG_DrawFrame(void) {
|
||||
/* Poll keyboard first */
|
||||
poll_keyboard();
|
||||
|
||||
/* Copy DG_ScreenBuffer (DOOMGENERIC_RESX x DOOMGENERIC_RESY) to framebuffer */
|
||||
if (g_fbPtr == 0 || DG_ScreenBuffer == 0) return;
|
||||
/* Copy DG_ScreenBuffer into the shared pixel buffer */
|
||||
if (g_pixBuf == 0 || DG_ScreenBuffer == 0) return;
|
||||
|
||||
uint32_t copyW = DOOMGENERIC_RESX;
|
||||
uint32_t copyH = DOOMGENERIC_RESY;
|
||||
if (copyW > g_fbWidth) copyW = g_fbWidth;
|
||||
if (copyH > g_fbHeight) copyH = g_fbHeight;
|
||||
memcpy(g_pixBuf, DG_ScreenBuffer,
|
||||
DOOMGENERIC_RESX * DOOMGENERIC_RESY * sizeof(uint32_t));
|
||||
|
||||
uint32_t fbStride = g_fbPitch / 4; /* pixels per scanline */
|
||||
|
||||
for (uint32_t y = 0; y < copyH; y++) {
|
||||
uint32_t* dst = g_fbPtr + y * fbStride;
|
||||
uint32_t* src = DG_ScreenBuffer + y * DOOMGENERIC_RESX;
|
||||
memcpy(dst, src, copyW * sizeof(uint32_t));
|
||||
}
|
||||
/* Mark window as dirty so the compositor picks it up */
|
||||
_zos_syscall1(SYS_WINPRESENT, (long)g_winId);
|
||||
}
|
||||
|
||||
void DG_SleepMs(uint32_t ms) {
|
||||
|
||||
+14
-48
@@ -712,27 +712,22 @@ int sprintf(char *buf, const char *fmt, ...) {
|
||||
static char _printbuf[4096];
|
||||
|
||||
int vprintf(const char *fmt, va_list ap) {
|
||||
int ret = vsnprintf(_printbuf, sizeof(_printbuf), fmt, ap);
|
||||
_zos_syscall1(SYS_PRINT, (long)_printbuf);
|
||||
return ret;
|
||||
(void)fmt; (void)ap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int printf(const char *fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int ret = vprintf(fmt, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
(void)fmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int puts(const char *s) {
|
||||
_zos_syscall1(SYS_PRINT, (long)s);
|
||||
_zos_syscall1(SYS_PUTCHAR, (long)'\n');
|
||||
(void)s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int putchar(int c) {
|
||||
_zos_syscall1(SYS_PUTCHAR, (long)c);
|
||||
(void)c;
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -862,20 +857,8 @@ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp) {
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp) {
|
||||
if (fp == NULL) return 0;
|
||||
|
||||
/* stdout/stderr: write to terminal */
|
||||
/* stdout/stderr: discard (no terminal in windowed mode) */
|
||||
if (fp->is_std == 1 || fp->is_std == 2) {
|
||||
/* Write as string to terminal */
|
||||
size_t total = size * nmemb;
|
||||
const char *s = (const char *)ptr;
|
||||
char buf[512];
|
||||
while (total > 0) {
|
||||
size_t chunk = total > 511 ? 511 : total;
|
||||
memcpy(buf, s, chunk);
|
||||
buf[chunk] = '\0';
|
||||
_zos_syscall1(SYS_PRINT, (long)buf);
|
||||
s += chunk;
|
||||
total -= chunk;
|
||||
}
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
@@ -970,31 +953,18 @@ char *fgets(char *s, int size, FILE *fp) {
|
||||
}
|
||||
|
||||
int fputs(const char *s, FILE *fp) {
|
||||
size_t len = strlen(s);
|
||||
return fwrite(s, 1, len, fp) > 0 ? 0 : -1;
|
||||
(void)s; (void)fp;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fprintf(FILE *fp, const char *fmt, ...) {
|
||||
char buf[4096];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
int ret = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (fp == stdout || fp == stderr || (fp && fp->is_std)) {
|
||||
_zos_syscall1(SYS_PRINT, (long)buf);
|
||||
}
|
||||
return ret;
|
||||
(void)fp; (void)fmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int vfprintf(FILE *fp, const char *fmt, va_list ap) {
|
||||
char buf[4096];
|
||||
int ret = vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
|
||||
if (fp == stdout || fp == stderr || (fp && fp->is_std)) {
|
||||
_zos_syscall1(SYS_PRINT, (long)buf);
|
||||
}
|
||||
return ret;
|
||||
(void)fp; (void)fmt; (void)ap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sscanf(const char *str, const char *fmt, ...) {
|
||||
@@ -1069,11 +1039,7 @@ int sscanf(const char *str, const char *fmt, ...) {
|
||||
}
|
||||
|
||||
void perror(const char *s) {
|
||||
if (s && *s) {
|
||||
_zos_syscall1(SYS_PRINT, (long)s);
|
||||
_zos_syscall1(SYS_PRINT, (long)": ");
|
||||
}
|
||||
_zos_syscall1(SYS_PRINT, (long)"error\n");
|
||||
(void)s;
|
||||
}
|
||||
|
||||
int rename(const char *old, const char *new_) {
|
||||
|
||||
Reference in New Issue
Block a user