feat: TrueType (TTF) font rendering, many new desktop applications and DOOM support, among other improvements

This commit is contained in:
2026-02-20 22:46:41 +01:00
parent a0db5899ef
commit 596be25eaf
124 changed files with 9021 additions and 355 deletions
+56
View File
@@ -66,6 +66,20 @@ namespace Zenith {
static constexpr uint64_t SYS_CHILDIO_WRITEKEY = 52;
static constexpr uint64_t SYS_CHILDIO_SETTERMSZ = 53;
// Window server syscalls
static constexpr uint64_t SYS_WINCREATE = 54;
static constexpr uint64_t SYS_WINDESTROY = 55;
static constexpr uint64_t SYS_WINPRESENT = 56;
static constexpr uint64_t SYS_WINPOLL = 57;
static constexpr uint64_t SYS_WINENUM = 58;
static constexpr uint64_t SYS_WINMAP = 59;
static constexpr uint64_t SYS_WINSENDEVENT = 60;
// Process management syscalls
static constexpr uint64_t SYS_PROCLIST = 61;
static constexpr uint64_t SYS_KILL = 62;
static constexpr uint64_t SYS_DEVLIST = 63;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
@@ -118,4 +132,46 @@ namespace Zenith {
uint8_t buttons;
};
// Window server shared types
struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close
uint8_t _pad[3];
union {
KeyEvent key;
struct { int32_t x, y, scroll; uint8_t buttons, prev_buttons; } mouse;
struct { int32_t w, h; } resize;
};
};
struct WinInfo {
int32_t id;
int32_t ownerPid;
char title[64];
int32_t width, height;
uint8_t dirty;
uint8_t _pad2[3];
};
struct WinCreateResult {
int32_t id; // -1 on failure
uint32_t _pad;
uint64_t pixelVa; // VA of pixel buffer in caller's address space
};
struct DevInfo {
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=PCI
uint8_t _pad[3];
char name[48];
char detail[48];
};
struct ProcInfo {
int32_t pid;
int32_t parentPid;
uint8_t state; // 0=Free, 1=Ready, 2=Running, 3=Terminated
uint8_t _pad[3];
char name[64];
uint64_t heapUsed; // heapNext - UserHeapBase (bytes)
};
}
+20 -2
View File
@@ -99,6 +99,10 @@ struct Canvas {
// ---- Text ----
void text(int x, int y, const char* str, Color c) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::UI_SIZE);
return;
}
uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
@@ -118,6 +122,10 @@ struct Canvas {
}
void text_2x(int x, int y, const char* str, Color c) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::LARGE_SIZE);
return;
}
uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH * 2 <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
@@ -141,6 +149,14 @@ struct Canvas {
}
}
void text_mono(int x, int y, const char* str, Color c) {
if (fonts::mono && fonts::mono->valid) {
fonts::mono->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::TERM_SIZE);
return;
}
text(x, y, str, c);
}
// ---- Icons ----
void icon(int x, int y, const SvgIcon& ic) {
@@ -176,7 +192,8 @@ struct Canvas {
// ---- High-level helpers ----
void kv_line(int x, int* y, const char* line, Color c, int line_h = FONT_HEIGHT + 6) {
void kv_line(int x, int* y, const char* line, Color c, int line_h = 0) {
if (line_h == 0) line_h = system_font_height() + 6;
text(x, *y, line, c);
*y += line_h;
}
@@ -190,8 +207,9 @@ struct Canvas {
Color bg, Color fg, int radius = 4) {
fill_rounded_rect(x, y, bw, bh, radius, bg);
int tw = text_width(label);
int fh = system_font_height();
int tx = x + (bw - tw) / 2;
int ty = y + (bh - FONT_HEIGHT) / 2;
int ty = y + (bh - fh) / 2;
text(tx, ty, label, fg);
}
};
+26
View File
@@ -18,6 +18,25 @@ namespace gui {
static constexpr int MAX_WINDOWS = 8;
static constexpr int PANEL_HEIGHT = 32;
struct DesktopSettings {
// Background
bool bg_gradient; // true = gradient, false = solid
Color bg_solid; // solid background color
Color bg_grad_top; // gradient top color
Color bg_grad_bottom; // gradient bottom color
// Panel
Color panel_color; // panel background color
// Accent
Color accent_color; // buttons, highlights, active indicators
// Display
bool show_shadows; // window shadows on/off
bool clock_24h; // 24-hour clock format
int ui_scale; // 0=Small, 1=Default, 2=Large
};
struct DesktopState {
Framebuffer fb;
Window windows[MAX_WINDOWS];
@@ -54,6 +73,11 @@ struct DesktopState {
SvgIcon icon_settings;
SvgIcon icon_reboot;
SvgIcon icon_doom;
SvgIcon icon_procmgr;
SvgIcon icon_mandelbrot;
SvgIcon icon_devexplorer;
bool ctx_menu_open;
int ctx_menu_x, ctx_menu_y;
@@ -63,6 +87,8 @@ struct DesktopState {
Rect net_icon_rect;
int screen_w, screen_h;
DesktopSettings settings;
};
// Forward declarations - implemented in main.cpp
+37 -1
View File
@@ -1,12 +1,13 @@
/*
* font.hpp
* ZenithOS 8x16 VGA bitmap font rendering
* ZenithOS text rendering — TrueType with bitmap fallback
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
#include "gui/truetype.hpp"
namespace gui {
@@ -16,6 +17,30 @@ static constexpr int FONT_HEIGHT = 16;
// Defined in font_data.cpp
extern const uint8_t font_data[256 * 16];
// Dynamic font height: TTF line height or 16 (bitmap fallback)
inline int system_font_height() {
if (fonts::system_font && fonts::system_font->valid)
return fonts::system_font->get_line_height(fonts::UI_SIZE);
return FONT_HEIGHT;
}
// Dynamic mono font cell dimensions
inline int mono_cell_width() {
if (fonts::mono && fonts::mono->valid) {
// Monospace: all glyphs have the same advance
GlyphCache* gc = fonts::mono->get_cache(fonts::TERM_SIZE);
CachedGlyph* g = fonts::mono->get_glyph(gc, 'M');
if (g) return g->advance;
}
return FONT_WIDTH;
}
inline int mono_cell_height() {
if (fonts::mono && fonts::mono->valid)
return fonts::mono->get_line_height(fonts::TERM_SIZE);
return FONT_HEIGHT;
}
inline void draw_char(Framebuffer& fb, int x, int y, char c, Color fg) {
const uint8_t* glyph = &font_data[(unsigned char)c * FONT_HEIGHT];
for (int row = 0; row < FONT_HEIGHT; row++) {
@@ -43,18 +68,29 @@ inline void draw_char_bg(Framebuffer& fb, int x, int y, char c, Color fg, Color
}
inline void draw_text(Framebuffer& fb, int x, int y, const char* text, Color fg) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw(fb, x, y, text, fg, fonts::UI_SIZE);
return;
}
for (int i = 0; text[i]; i++) {
draw_char(fb, x + i * FONT_WIDTH, y, text[i], fg);
}
}
inline void draw_text_bg(Framebuffer& fb, int x, int y, const char* text, Color fg, Color bg) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_bg(fb, x, y, text, fg, bg, fonts::UI_SIZE);
return;
}
for (int i = 0; text[i]; i++) {
draw_char_bg(fb, x + i * FONT_WIDTH, y, text[i], fg, bg);
}
}
inline int text_width(const char* text) {
if (fonts::system_font && fonts::system_font->valid) {
return fonts::system_font->measure_text(text, fonts::UI_SIZE);
}
int len = 0;
while (text[len]) len++;
return len * FONT_WIDTH;
+4 -4
View File
@@ -25,13 +25,13 @@ inline fixed_t fixed_from_parts(int whole, int frac_num, int frac_den) {
struct Color {
uint8_t r, g, b, a;
static Color from_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; }
static Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; }
static Color from_hex(uint32_t hex) {
static constexpr Color from_rgb(uint8_t r, uint8_t g, uint8_t b) { return {r, g, b, 255}; }
static constexpr Color from_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return {r, g, b, a}; }
static constexpr Color from_hex(uint32_t hex) {
return {(uint8_t)((hex >> 16) & 0xFF), (uint8_t)((hex >> 8) & 0xFF), (uint8_t)(hex & 0xFF), 255};
}
uint32_t to_pixel() const { return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; }
constexpr uint32_t to_pixel() const { return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; }
};
// Named colors for the desktop theme
+95
View File
@@ -0,0 +1,95 @@
/*
* stb_math.h
* Math functions for stb_truetype in freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#ifndef STB_MATH_H
#define STB_MATH_H
#ifdef __cplusplus
extern "C" {
#endif
static inline double stb_floor(double x) {
double i = (double)(long long)x;
return (x < i) ? i - 1.0 : i;
}
static inline double stb_ceil(double x) {
double f = stb_floor(x);
return (x > f) ? f + 1.0 : f;
}
static inline double stb_fabs(double x) {
return x < 0.0 ? -x : x;
}
static inline double stb_fmod(double x, double y) {
if (y == 0.0) return 0.0;
return x - (double)((long long)(x / y)) * y;
}
static inline double stb_sqrt(double x) {
if (x <= 0.0) return 0.0;
double guess = x;
for (int i = 0; i < 30; i++)
guess = (guess + x / guess) * 0.5;
return guess;
}
static inline double stb_pow(double base, double exp) {
if (exp == 0.0) return 1.0;
if (exp == 1.0) return base;
if (base == 0.0) return 0.0;
// Integer exponent fast path
if (exp == (double)(long long)exp) {
long long e = (long long)exp;
int neg = 0;
if (e < 0) { neg = 1; e = -e; }
double r = 1.0;
double b = base;
while (e > 0) {
if (e & 1) r *= b;
b *= b;
e >>= 1;
}
return neg ? 1.0 / r : r;
}
return 0.0;
}
static inline double stb_cos(double x) {
// Reduce to [0, 2*pi]
const double PI = 3.14159265358979323846;
const double TWO_PI = 6.28318530717958647692;
x = stb_fmod(stb_fabs(x), TWO_PI);
// Taylor series: cos(x) = 1 - x^2/2! + x^4/4! - x^6/6! + ...
double x2 = x * x;
double term = 1.0;
double result = 1.0;
for (int i = 1; i <= 10; i++) {
term *= -x2 / (double)((2 * i - 1) * (2 * i));
result += term;
}
return result;
}
static inline double stb_acos(double x) {
// Clamp input
if (x <= -1.0) return 3.14159265358979323846;
if (x >= 1.0) return 0.0;
// Polynomial approximation (Abramowitz & Stegun style)
double ax = stb_fabs(x);
double result = (-0.0187293 * ax + 0.0742610) * ax - 0.2121144;
result = (result * ax + 1.5707288) * stb_sqrt(1.0 - ax);
if (x < 0.0)
return 3.14159265358979323846 - result;
return result;
}
#ifdef __cplusplus
}
#endif
#endif // STB_MATH_H
File diff suppressed because it is too large Load Diff
+101 -32
View File
@@ -423,6 +423,11 @@ static inline void terminal_feed(TerminalState* t, const char* data, int len) {
}
static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, int ph) {
int cell_w = mono_cell_width();
int cell_h = mono_cell_height();
bool use_ttf = fonts::mono && fonts::mono->valid;
GlyphCache* gc = use_ttf ? fonts::mono->get_cache(fonts::TERM_SIZE) : nullptr;
// Fill background
uint32_t bg_px = colors::TERM_BG.to_pixel();
int total = pw * ph;
@@ -431,8 +436,8 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
}
// Render each visible cell
int visible_rows = ph / FONT_HEIGHT;
int visible_cols = pw / FONT_WIDTH;
int visible_rows = ph / cell_h;
int visible_cols = pw / cell_w;
if (visible_rows > t->rows) visible_rows = t->rows;
if (visible_cols > t->cols) visible_cols = t->cols;
@@ -441,17 +446,16 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
int idx = r * t->cols + c;
TermCell& cell = t->cells[idx];
int px = c * FONT_WIDTH;
int py = r * FONT_HEIGHT;
int px = c * cell_w;
int py = r * cell_h;
uint32_t cell_bg = cell.bg.to_pixel();
uint32_t cell_fg = cell.fg.to_pixel();
// Draw cell background
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
for (int fy = 0; fy < cell_h; fy++) {
int dy = py + fy;
if (dy >= ph) break;
for (int fx = 0; fx < FONT_WIDTH; fx++) {
for (int fx = 0; fx < cell_w; fx++) {
int dx = px + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cell_bg;
@@ -460,16 +464,23 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
// Draw character glyph
if (cell.ch > 32 || cell.ch < 0) {
const uint8_t* glyph = &font_data[(unsigned char)cell.ch * FONT_HEIGHT];
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = py + fy;
if (dy >= ph) break;
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = px + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cell_fg;
if (use_ttf) {
int baseline = py + gc->ascent;
fonts::mono->draw_char_to_buffer(pixels, pw, ph,
px, baseline, (unsigned char)cell.ch, cell.fg, gc);
} else {
uint32_t cell_fg = cell.fg.to_pixel();
const uint8_t* glyph = &font_data[(unsigned char)cell.ch * FONT_HEIGHT];
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = py + fy;
if (dy >= ph) break;
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = px + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cell_fg;
}
}
}
}
@@ -479,13 +490,13 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
// Draw cursor
if (t->cursor_visible && t->cursor_x < visible_cols && t->cursor_y < visible_rows) {
int cx = t->cursor_x * FONT_WIDTH;
int cy = t->cursor_y * FONT_HEIGHT;
int cx = t->cursor_x * cell_w;
int cy = t->cursor_y * cell_h;
uint32_t cursor_px = colors::WHITE.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
for (int fy = 0; fy < cell_h; fy++) {
int dy = cy + fy;
if (dy >= ph) break;
for (int fx = 0; fx < FONT_WIDTH; fx++) {
for (int fx = 0; fx < cell_w; fx++) {
int dx = cx + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = cursor_px;
@@ -496,17 +507,23 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
int idx = t->cursor_y * t->cols + t->cursor_x;
char ch = t->cells[idx].ch;
if (ch > 32 || ch < 0) {
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
uint32_t black_px = colors::BLACK.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = cy + fy;
if (dy >= ph) break;
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = black_px;
if (use_ttf) {
int baseline = cy + gc->ascent;
fonts::mono->draw_char_to_buffer(pixels, pw, ph,
cx, baseline, (unsigned char)ch, colors::BLACK, gc);
} else {
const uint8_t* glyph = &font_data[(unsigned char)ch * FONT_HEIGHT];
uint32_t black_px = colors::BLACK.to_pixel();
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
int dy = cy + fy;
if (dy >= ph) break;
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx;
if (dx >= pw) break;
pixels[dy * pw + dx] = black_px;
}
}
}
}
@@ -515,6 +532,58 @@ static inline void terminal_render(TerminalState* t, uint32_t* pixels, int pw, i
}
}
static inline void terminal_resize(TerminalState* t, int new_cols, int new_rows) {
if (new_cols == t->cols && new_rows == t->rows) return;
if (new_cols < 1 || new_rows < 1) return;
int new_total = new_cols * new_rows;
TermCell* new_cells = (TermCell*)zenith::alloc(new_total * sizeof(TermCell));
TermCell* new_alt = (TermCell*)zenith::alloc(new_total * sizeof(TermCell));
// Clear new buffers
for (int i = 0; i < new_total; i++) {
new_cells[i] = {' ', colors::TERM_FG, colors::TERM_BG};
new_alt[i] = {' ', colors::TERM_FG, colors::TERM_BG};
}
// Copy existing content (as much as fits)
int copy_rows = t->rows < new_rows ? t->rows : new_rows;
int copy_cols = t->cols < new_cols ? t->cols : new_cols;
// If cursor is beyond the new grid, scroll content up to keep cursor visible
int row_offset = 0;
if (t->cursor_y >= new_rows) {
row_offset = t->cursor_y - new_rows + 1;
}
for (int r = 0; r < copy_rows && (r + row_offset) < t->rows; r++) {
for (int c = 0; c < copy_cols; c++) {
new_cells[r * new_cols + c] = t->cells[(r + row_offset) * t->cols + c];
}
}
if (t->cells) zenith::mfree(t->cells);
if (t->alt_cells) zenith::mfree(t->alt_cells);
t->cells = new_cells;
t->alt_cells = new_alt;
int old_cols = t->cols;
t->cols = new_cols;
t->rows = new_rows;
// Adjust cursor position
t->cursor_y -= row_offset;
if (t->cursor_x >= new_cols) t->cursor_x = new_cols - 1;
if (t->cursor_y >= new_rows) t->cursor_y = new_rows - 1;
if (t->cursor_y < 0) t->cursor_y = 0;
// Notify child process of new terminal size
if (t->child_pid > 0) {
zenith::childio_settermsz(t->child_pid, new_cols, new_rows);
}
}
static inline void terminal_handle_key(TerminalState* t, const Zenith::KeyEvent& key) {
if (t->child_pid > 0) {
zenith::childio_writekey(t->child_pid, &key);
+335
View File
@@ -0,0 +1,335 @@
/*
* truetype.hpp
* ZenithOS TrueType font rendering via stb_truetype
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <zenith/syscall.h>
#include <zenith/heap.h>
#include <zenith/string.h>
#include "gui/gui.hpp"
#include "gui/framebuffer.hpp"
// Forward-declare stbtt_fontinfo to avoid including stb_truetype.h in every TU.
// The actual struct is defined in stb_truetype.h and only used in truetype.hpp
// method bodies (which are inline but only instantiated where stb_truetype.h
// is also included — i.e. stb_truetype_impl.cpp). We include the full header
// here since our inline methods need the complete type.
#include "gui/stb_math.h"
// We need the stb macros defined before including stb_truetype.h (header-only mode)
#ifndef STBTT_ifloor
#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))
#endif
#include "gui/stb_truetype.h"
namespace gui {
struct CachedGlyph {
uint8_t* bitmap;
int width, height;
int xoff, yoff;
int advance;
bool loaded;
};
struct GlyphCache {
CachedGlyph glyphs[128];
int pixel_size;
float scale;
int ascent, descent, line_gap;
int line_height;
};
struct TrueTypeFont {
stbtt_fontinfo info;
uint8_t* data;
GlyphCache caches[4];
int cache_count;
bool valid;
bool init(const char* vfs_path) {
valid = false;
data = nullptr;
cache_count = 0;
int fd = zenith::open(vfs_path);
if (fd < 0) return false;
uint64_t size = zenith::getsize(fd);
if (size == 0 || size > 1024 * 1024) {
zenith::close(fd);
return false;
}
data = (uint8_t*)zenith::alloc(size);
if (!data) {
zenith::close(fd);
return false;
}
zenith::read(fd, data, 0, size);
zenith::close(fd);
if (!stbtt_InitFont(&info, data, stbtt_GetFontOffsetForIndex(data, 0))) {
zenith::free(data);
data = nullptr;
return false;
}
valid = true;
return true;
}
GlyphCache* get_cache(int pixel_size) {
// Search existing caches
for (int i = 0; i < cache_count; i++) {
if (caches[i].pixel_size == pixel_size)
return &caches[i];
}
// Create new cache
if (cache_count >= 4) return &caches[0]; // fallback to first
GlyphCache* gc = &caches[cache_count++];
gc->pixel_size = pixel_size;
gc->scale = stbtt_ScaleForPixelHeight(&info, (float)pixel_size);
int asc, desc, lg;
stbtt_GetFontVMetrics(&info, &asc, &desc, &lg);
gc->ascent = (int)(asc * gc->scale);
gc->descent = (int)(desc * gc->scale);
gc->line_gap = (int)(lg * gc->scale);
gc->line_height = gc->ascent - gc->descent + gc->line_gap;
for (int i = 0; i < 128; i++) {
gc->glyphs[i].bitmap = nullptr;
gc->glyphs[i].loaded = false;
}
return gc;
}
CachedGlyph* get_glyph(GlyphCache* gc, int codepoint) {
if (codepoint < 0 || codepoint >= 128) return nullptr;
CachedGlyph* g = &gc->glyphs[codepoint];
if (g->loaded) return g;
g->loaded = true;
int advance, lsb;
stbtt_GetCodepointHMetrics(&info, codepoint, &advance, &lsb);
g->advance = (int)(advance * gc->scale);
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&info, codepoint, gc->scale, gc->scale,
&x0, &y0, &x1, &y1);
g->width = x1 - x0;
g->height = y1 - y0;
g->xoff = x0;
g->yoff = y0;
if (g->width > 0 && g->height > 0) {
g->bitmap = (uint8_t*)zenith::malloc(g->width * g->height);
stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height,
g->width, gc->scale, gc->scale, codepoint);
}
return g;
}
int measure_text(const char* text, int pixel_size) {
if (!valid) return 0;
GlyphCache* gc = get_cache(pixel_size);
int w = 0;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
if (g) w += g->advance;
}
return w;
}
int get_line_height(int pixel_size) {
if (!valid) return 16;
GlyphCache* gc = get_cache(pixel_size);
return gc->line_height;
}
void draw(Framebuffer& fb, int x, int y, const char* text,
Color color, int pixel_size) {
if (!valid) return;
GlyphCache* gc = get_cache(pixel_size);
int cx = x;
int baseline = y + gc->ascent;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
if (!g) continue;
if (g->bitmap) {
int gx = cx + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
for (int col = 0; col < g->width; col++) {
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha > 0) {
Color c = {color.r, color.g, color.b, alpha};
fb.put_pixel_alpha(gx + col, gy + row, c);
}
}
}
}
cx += g->advance;
}
}
void draw_bg(Framebuffer& fb, int x, int y, const char* text,
Color fg, Color bg, int pixel_size) {
if (!valid) return;
GlyphCache* gc = get_cache(pixel_size);
// Fill background for the text extent
int tw = measure_text(text, pixel_size);
fb.fill_rect(x, y, tw, gc->line_height, bg);
// Then draw foreground
draw(fb, x, y, text, fg, pixel_size);
}
void draw_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
int x, int y, const char* text,
Color color, int pixel_size) {
if (!valid) return;
GlyphCache* gc = get_cache(pixel_size);
int cx = x;
int baseline = y + gc->ascent;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
if (!g) continue;
if (g->bitmap) {
int gx = cx + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
int dy = gy + row;
if (dy < 0 || dy >= buf_h) continue;
for (int col = 0; col < g->width; col++) {
int dx = gx + col;
if (dx < 0 || dx >= buf_w) continue;
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha == 0) continue;
if (alpha == 255) {
pixels[dy * buf_w + dx] =
0xFF000000 | ((uint32_t)color.r << 16) |
((uint32_t)color.g << 8) | color.b;
} else {
uint32_t dst = pixels[dy * buf_w + dx];
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = dst & 0xFF;
uint32_t a = alpha, inv_a = 255 - alpha;
uint32_t rr = (a * color.r + inv_a * dr + 128) / 255;
uint32_t gg = (a * color.g + inv_a * dg + 128) / 255;
uint32_t bb = (a * color.b + inv_a * db + 128) / 255;
pixels[dy * buf_w + dx] =
0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
}
cx += g->advance;
}
}
// Draw single character to buffer, returning advance width
int draw_char_to_buffer(uint32_t* pixels, int buf_w, int buf_h,
int x, int baseline, int codepoint,
Color color, GlyphCache* gc) {
CachedGlyph* g = get_glyph(gc, codepoint);
if (!g) return 0;
if (g->bitmap) {
int gx = x + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
int dy = gy + row;
if (dy < 0 || dy >= buf_h) continue;
for (int col = 0; col < g->width; col++) {
int dx = gx + col;
if (dx < 0 || dx >= buf_w) continue;
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha == 0) continue;
if (alpha == 255) {
pixels[dy * buf_w + dx] =
0xFF000000 | ((uint32_t)color.r << 16) |
((uint32_t)color.g << 8) | color.b;
} else {
uint32_t dst = pixels[dy * buf_w + dx];
uint8_t dr = (dst >> 16) & 0xFF;
uint8_t dg = (dst >> 8) & 0xFF;
uint8_t db = dst & 0xFF;
uint32_t a = alpha, inv_a = 255 - alpha;
uint32_t rr = (a * color.r + inv_a * dr + 128) / 255;
uint32_t gg = (a * color.g + inv_a * dg + 128) / 255;
uint32_t bb = (a * color.b + inv_a * db + 128) / 255;
pixels[dy * buf_w + dx] =
0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
}
return g->advance;
}
};
// Global font manager
namespace fonts {
inline TrueTypeFont* system_font = nullptr;
inline TrueTypeFont* system_bold = nullptr;
inline TrueTypeFont* mono = nullptr;
inline TrueTypeFont* mono_bold = nullptr;
inline int UI_SIZE = 18;
inline int TITLE_SIZE = 18;
inline int TERM_SIZE = 18;
inline int LARGE_SIZE = 28;
inline bool init() {
auto load = [](const char* path) -> TrueTypeFont* {
TrueTypeFont* f = (TrueTypeFont*)zenith::malloc(sizeof(TrueTypeFont));
zenith::memset(f, 0, sizeof(TrueTypeFont));
if (!f->init(path)) {
zenith::mfree(f);
return nullptr;
}
return f;
};
system_font = load("0:/fonts/Roboto-Medium.ttf");
system_bold = load("0:/fonts/Roboto-Bold.ttf");
mono = load("0:/fonts/JetBrainsMono-Regular.ttf");
mono_bold = load("0:/fonts/JetBrainsMono-Bold.ttf");
return system_font != nullptr;
}
}
} // namespace gui
+10 -5
View File
@@ -79,7 +79,7 @@ struct Button {
// Center text
int tw = text_width(text);
int tx = bounds.x + (bounds.w - tw) / 2;
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2;
int ty = bounds.y + (bounds.h - system_font_height()) / 2;
draw_text(fb, tx, ty, text, fg);
}
@@ -150,7 +150,7 @@ struct IconButton {
// Draw text
if (text) {
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2;
int ty = bounds.y + (bounds.h - system_font_height()) / 2;
draw_text(fb, content_x, ty, text, text_color);
}
}
@@ -206,13 +206,18 @@ struct TextBox {
// Draw text with 4px padding
int tx = bounds.x + 4;
int ty = bounds.y + (bounds.h - FONT_HEIGHT) / 2;
int fh = system_font_height();
int ty = bounds.y + (bounds.h - fh) / 2;
draw_text(fb, tx, ty, text, fg);
// Draw cursor if focused
if (focused) {
int cx = tx + cursor * FONT_WIDTH;
draw_vline(fb, cx, ty, FONT_HEIGHT, cursor_color);
// Measure text up to cursor position for proportional fonts
char prefix[256];
for (int i = 0; i < cursor && i < 255; i++) prefix[i] = text[i];
prefix[cursor < 255 ? cursor : 255] = '\0';
int cx = tx + text_width(prefix);
draw_vline(fb, cx, ty, fh, cursor_color);
}
}
+3
View File
@@ -64,6 +64,9 @@ struct Window {
WindowPollCallback on_poll;
void* app_data;
bool external; // true = shared-memory window from external process
int ext_win_id; // window server ID (valid when external == true)
Rect titlebar_rect() const {
return {frame.x, frame.y, frame.w, TITLEBAR_HEIGHT};
}
+34
View File
@@ -284,4 +284,38 @@ namespace zenith {
return (int)syscall3(Zenith::SYS_CHILDIO_SETTERMSZ, (uint64_t)childPid, (uint64_t)cols, (uint64_t)rows);
}
// Process listing / kill
inline int proclist(Zenith::ProcInfo* buf, int max) {
return (int)syscall2(Zenith::SYS_PROCLIST, (uint64_t)buf, (uint64_t)max);
}
inline int kill(int pid) {
return (int)syscall1(Zenith::SYS_KILL, (uint64_t)pid);
}
inline int devlist(Zenith::DevInfo* buf, int max) {
return (int)syscall2(Zenith::SYS_DEVLIST, (uint64_t)buf, (uint64_t)max);
}
// Window server
inline int win_create(const char* title, int w, int h, Zenith::WinCreateResult* result) {
return (int)syscall4(Zenith::SYS_WINCREATE, (uint64_t)title, (uint64_t)w, (uint64_t)h, (uint64_t)result);
}
inline int win_destroy(int id) {
return (int)syscall1(Zenith::SYS_WINDESTROY, (uint64_t)id);
}
inline int win_present(int id) {
return (int)syscall1(Zenith::SYS_WINPRESENT, (uint64_t)id);
}
inline int win_poll(int id, Zenith::WinEvent* event) {
return (int)syscall2(Zenith::SYS_WINPOLL, (uint64_t)id, (uint64_t)event);
}
inline int win_enumerate(Zenith::WinInfo* info, int max) {
return (int)syscall2(Zenith::SYS_WINENUM, (uint64_t)info, (uint64_t)max);
}
inline uint64_t win_map(int id) {
return (uint64_t)syscall1(Zenith::SYS_WINMAP, (uint64_t)id);
}
inline int win_sendevent(int id, const Zenith::WinEvent* event) {
return (int)syscall2(Zenith::SYS_WINSENDEVENT, (uint64_t)id, (uint64_t)event);
}
}