Glyph Widget Toolkit
Retained-mode widget toolkit with dirty-rect propagation, dark-mode frosted glass aesthetic, and TTF font rendering
Glyph Widget Toolkit
Glyph is the widget toolkit for the Aegis graphical environment. It provides a retained-mode widget tree with automatic dirty-rect propagation, layout containers, and a dark-mode visual language designed to blend with Lumen’s frosted glass compositing. Glyph renders directly to surface_t pixel buffers – it has no dependency on a display server protocol, GPU driver, or rendering library beyond its own software rasterizer.
v1 note: Glyph is v1 software – functional and tested, but still evolving. Contributions are welcome – file issues or propose changes at exec/aegis.
Glyph library architecture:
+----------------------------------------------------------+
| glyph_window_t |
| +------------------------------------------------------+|
| | Chrome: titlebar, traffic-light buttons, border ||
| | (render_chrome in window.c) ||
| +------------------------------------------------------+|
| | Client area ||
| | +--------------------------------------------------+ ||
| | | glyph_widget_t tree (retained mode) | ||
| | | draw_fn(), on_mouse(), on_key(), on_destroy() | ||
| | | automatic dirty propagation to window | ||
| | +--------------------------------------------------+ ||
| +------------------------------------------------------+|
| | surface_t: offscreen XRGB pixel buffer ||
| | dirty_rect: accumulated dirty region for compositor ||
+----------------------------------------------------------+
|
v
+----------------------------------------------------------+
| draw.c: Software rasterizer primitives |
| font.c: stb_truetype TTF renderer with atlas caching |
| terminus20.h: Bitmap fallback font (10x20) |
+----------------------------------------------------------+
Core Concepts
Surface
All drawing targets a surface_t:
typedef struct {
uint32_t *buf;
int w, h, pitch; /* pitch in pixels */
} surface_t;
Pixels are 32-bit XRGB (top byte unused). pitch is in pixels, not bytes, allowing framebuffers with stride padding. All drawing primitives take a surface_t * and perform bounds checking.
Widget Base Type
Every widget embeds glyph_widget_t as its first member, enabling safe casting between base and concrete types:
struct glyph_widget {
glyph_widget_type_t type;
glyph_widget_t *parent;
glyph_widget_t *children[GLYPH_MAX_CHILDREN]; /* max 32 */
int nchildren;
int x, y, w, h; /* bounds relative to parent */
int pref_w, pref_h; /* preferred size for layout */
int dirty;
int visible;
int focusable;
int hovered; /* set by compositor hover tracking */
void (*draw_fn)(glyph_widget_t *self, surface_t *surf, int ox, int oy);
void (*on_mouse)(glyph_widget_t *self, int btn, int local_x, int local_y);
void (*on_key)(glyph_widget_t *self, char key);
void (*on_destroy)(glyph_widget_t *self);
glyph_window_t *window; /* owning window (set recursively) */
};
The type field is used for runtime type identification – layout containers check type == GLYPH_WIDGET_HBOX || type == GLYPH_WIDGET_VBOX to trigger auto-layout when children are added.
Widget Type Enum
typedef enum {
GLYPH_WIDGET_LABEL, GLYPH_WIDGET_BUTTON,
GLYPH_WIDGET_TEXTFIELD, GLYPH_WIDGET_CHECKBOX,
GLYPH_WIDGET_LISTVIEW, GLYPH_WIDGET_SCROLLBAR,
GLYPH_WIDGET_IMAGE, GLYPH_WIDGET_PROGRESS,
GLYPH_WIDGET_MENUBAR, GLYPH_WIDGET_TABS,
GLYPH_WIDGET_HBOX, GLYPH_WIDGET_VBOX,
GLYPH_WIDGET_SLIDER, GLYPH_WIDGET_RADIO,
GLYPH_WIDGET_SEPARATOR, GLYPH_WIDGET_TOGGLE,
} glyph_widget_type_t;
Widget Tree Operations
Tree Construction
glyph_widget_init(w, type); /* zero + set type, visible=1, dirty=1 */
glyph_widget_add_child(parent, child); /* append child, auto-layout if box */
glyph_widget_remove_child(parent, child);
glyph_widget_set_window(root, win); /* recursive window pointer assignment */
glyph_widget_add_child is the primary tree-building operation. When the parent is an HBox or VBox, adding a child immediately triggers glyph_box_layout to recompute positions.
Dirty Rect Propagation
When a widget is marked dirty, it computes its absolute position by walking up the parent chain and reports the dirty rectangle to the owning window:
void glyph_widget_mark_dirty(glyph_widget_t *w)
{
w->dirty = 1;
if (w->window) {
/* Walk parent chain to compute absolute offset */
int ax = 0, ay = 0;
glyph_widget_t *p = w;
while (p) { ax += p->x; ay += p->y; p = p->parent; }
glyph_rect_t r = { ax, ay, w->w, w->h };
glyph_window_mark_dirty_rect(w->window, r);
}
}
The window accumulates dirty rects via union. During the next compositor frame, the window’s dirty rect is converted to screen coordinates and fed to the compositor’s dirty rect system.
Hit Testing
Hit testing is depth-first, reverse-order (topmost child wins):
glyph_widget_t *glyph_widget_hit_test(glyph_widget_t *root, int x, int y);
The algorithm recursively descends through the widget tree. At each level, children are tested in reverse order (highest index first) since later children are drawn on top. If no child matches, the parent widget itself is returned. Coordinates are relative to the parent’s origin at each level.
Focus Cycling
Tab key cycling walks the widget tree depth-first to find the next focusable widget:
glyph_widget_t *glyph_widget_focus_next(root, current);
If current is NULL, returns the first focusable widget. After the last focusable widget, wraps to the first.
Tree Rendering
void glyph_widget_draw_tree(root, surf, ox, oy);
Recursive pre-order traversal: draws the parent first (via draw_fn), then each child with accumulated offset. Clears each widget’s dirty flag after drawing.
Window System
glyph_window_t
struct glyph_window {
int x, y; /* screen position (top-left of border) */
int client_w, client_h; /* client area dimensions */
int surf_w, surf_h; /* total surface including chrome */
surface_t surface; /* offscreen pixel buffer */
char title[64];
glyph_widget_t *root; /* root widget for client area */
glyph_widget_t *focused; /* focused widget (receives keys) */
glyph_rect_t dirty_rect; /* accumulated dirty region */
int has_dirty;
int visible;
int closeable;
int focused_window; /* 1 if compositor's active window */
int frosted; /* 1 = frosted glass compositing */
int chromeless; /* 1 = no titlebar/border */
void (*on_key)(glyph_window_t *self, char key);
void (*on_render)(glyph_window_t *self); /* custom content render */
void (*on_mouse_down)(glyph_window_t *self, int x, int y);
void (*on_mouse_move)(glyph_window_t *self, int x, int y);
void (*on_mouse_up)(glyph_window_t *self, int x, int y);
void (*on_scroll)(glyph_window_t *self, int direction);
void *priv; /* opaque data (terminal state, etc.) */
int tag; /* generic int (PTY master fd) */
};
Window Chrome
Chrome constants:
| Constant | Value | Purpose |
|---|---|---|
GLYPH_TITLEBAR_HEIGHT |
30 | Titlebar height in pixels |
GLYPH_BORDER_WIDTH |
1 | Border width around window |
GLYPH_SHADOW_OFFSET |
0 | No drop shadow (frosted glass replaces it) |
CORNER_R |
10 | Rounded corner radius |
Surface dimensions are computed as:
surf_w = client_w + 2 * BORDER_WIDTHsurf_h = client_h + TITLEBAR_HEIGHT + 2 * BORDER_WIDTH
Chrome rendering (render_chrome in window.c) draws:
- Rounded body using
draw_rounded_rectfor frosted windows (single key color) or per-pixel corner tests for non-frosted (two-color titlebar/client) - Divider line between titlebar and client area
- Title text centered horizontally (TTF at 13px or bitmap fallback)
- Traffic-light buttons: three filled circles (red close, yellow minimize, green maximize) with 22px spacing, 7px radius
For frosted windows, the entire surface is filled with C_SHADOW (the color key). The compositor’s blur+tint shows through where this key color appears, and actual content is drawn on top.
Window Render Pipeline
void glyph_window_render(glyph_window_t *win)
{
render_chrome(win); /* border, titlebar, buttons */
if (win->on_render)
win->on_render(win); /* custom content (terminal) */
if (win->root)
glyph_widget_draw_tree(win->root, &win->surface, ox, oy);
}
The custom on_render callback is called AFTER chrome but BEFORE the widget tree. This ordering allows terminal windows (which render directly, not via widgets) to draw into the client area without being overwritten by chrome.
Input Dispatch
Mouse: glyph_window_dispatch_mouse converts screen coordinates to client-area coordinates, checks close button, then hit-tests the widget tree. The hit widget receives focus (if focusable) and an on_mouse callback with local coordinates.
Keyboard: glyph_window_dispatch_key first checks for a window-level on_key handler (used by terminal windows). If absent, Tab cycles focus, and other keys are forwarded to the focused widget’s on_key.
Hover Tracking
The compositor calls glyph_window_update_hover on the focused window each frame when the mouse moves:
void glyph_window_update_hover(win, screen_x, screen_y);
This walks the widget tree with an iterative stack (max 64 depth), updating hovered flags and marking dirty any widget whose hover state changed. Buttons use this flag to switch between normal and hover background colors.
Widget Catalog
Label (label.c)
Non-interactive text display.
glyph_label_t *glyph_label_create(const char *text, uint32_t color);
void glyph_label_set_text(glyph_label_t *label, const char *text);
Labels default to transparent mode (transparent = 1) so the frosted glass background shows through. When opaque, they fill their bounds with bg_color before drawing text. Text is rendered via draw_text_ui (TTF at 14px if available, bitmap fallback otherwise). Maximum text length: 127 characters.
Button (button.c)
Clickable button with four states: Normal, Hover, Pressed, Disabled.
glyph_button_t *glyph_button_create(const char *text, void (*on_click)(glyph_widget_t *));
void glyph_button_set_state(btn, GLYPH_BTN_DISABLED);
Button rendering uses alpha-blended backgrounds rather than solid fills to maintain the frosted glass aesthetic:
| State | Background | Alpha | Text Color |
|---|---|---|---|
| Normal | 0x002A3850 |
180 | 0x00E8E8F0 |
| Hover | 0x00344868 |
180 | 0x00E8E8F0 |
| Pressed | 0x00203040 |
180 | 0x00E8E8F0 |
| Disabled | 0x00282830 |
180 | 0x00606068 |
Hover state is driven automatically by the compositor’s glyph_window_update_hover mechanism – no explicit state management needed. The pressed state adds a 1px text offset for a sunken effect. Border highlights: top and left edges get a subtle white blend (alpha 15-20), bottom and right get a dark blend (alpha 20-30).
Preferred size: text_width + 2 * 12px horizontal, text_height + 2 * 4px vertical.
Text Field (textfield.c)
Single-line text input with cursor.
glyph_textfield_t *glyph_textfield_create(int width_chars, void (*on_change)(...));
void glyph_textfield_set_mask(tf, '*'); /* password mode */
Features:
- Cursor rendered as a 1px vertical accent-colored line
- Click positions the cursor at the clicked character
- Backspace deletes before cursor with
memmove - Enter fires
on_changewithout insertion - Printable characters (32-126) inserted at cursor position
- Password mask mode replaces visible characters
- Maximum buffer: 255 characters
The field background uses draw_blend_rect with alpha 80 (dark glass look). Border: top/left are dark inset, bottom/right are light highlight.
Checkbox (checkbox.c)
Toggle checkbox with label.
glyph_checkbox_t *glyph_checkbox_create(const char *label, void (*on_change)(..., int checked));
The check box is 16x16 pixels. When checked, an X-shaped mark is drawn using accent color (C_ACCENT = 0x004488CC). The box background uses a subtle dark blend. The label is drawn 6px to the right of the box, vertically centered.
Progress Bar (progress.c)
Horizontal progress indicator (0-100%).
glyph_progress_t *glyph_progress_create(int width);
void glyph_progress_set_value(bar, 50);
Height is fixed at 20px. The filled portion uses draw_blend_rect with the accent color at alpha 200. The background is dark glass (alpha 60). Not focusable.
List View (listview.c)
Scrollable list with integrated scrollbar.
glyph_listview_t *glyph_listview_create(int width, int visible_rows, void (*on_select)(...));
void glyph_listview_set_items(lv, items, count);
Maximum items: GLYPH_LISTVIEW_MAX_ITEMS = 256. The selected row gets a subtle light blend (alpha 25). Keyboard navigation uses vi-style keys (j/k or n/p) since single-char input doesn’t support arrow keys. The scrollbar (16px wide) is created as a child widget and updated automatically.
The list background is filled with C_SHADOW first (to prevent blend accumulation on repeated redraws), then a dark glass blend is applied on top.
Scrollbar (scrollbar.c)
Vertical scroll bar (typically used internally by List View).
glyph_scrollbar_t *glyph_scrollbar_create(int height, void (*on_scroll)(...));
void glyph_scrollbar_set_range(sb, max_val, page_size);
The thumb height is proportional to page_size / (range + page_size), with a minimum of 20px. Clicking anywhere on the track sets the scroll position. Not focusable.
Tabs (tabs.c)
Tabbed container that shows one panel at a time.
glyph_tabs_t *glyph_tabs_create(void (*on_change)(..., int index));
void glyph_tabs_add(tabs, "Label", panel_widget);
Maximum tabs: GLYPH_TABS_MAX = 8. Inactive panel widgets have visible = 0, so the tree walker skips them. The active tab gets a lighter blend highlight. Tab header height: text_height + 8px. The tab container’s dimensions are computed as the max of total header width vs widest panel, plus header height plus tallest panel.
Menu Bar (menubar.c)
Horizontal menu bar with dropdown menus.
glyph_menubar_t *glyph_menubar_create(void (*on_select)(..., int menu_idx, int item_idx));
void glyph_menubar_add_menu(mb, "File", items, count);
Maximum menus: 8. Maximum items per menu: 16. Clicking a label toggles the dropdown; clicking an item fires on_select and closes the menu. Escape closes the open menu. The dropdown width is the maximum of item text widths and the label width.
Image (image.c)
Raw pixel buffer display.
glyph_image_t *glyph_image_create(uint32_t *pixels, int w, int h);
Blits the pixel buffer directly to the surface via draw_blit. Does not own the pixel data. Not focusable.
Layout Containers (box.c)
HBox and VBox arrange children horizontally or vertically.
glyph_box_t *glyph_hbox_create(int padding, int spacing);
glyph_box_t *glyph_vbox_create(int padding, int spacing);
Layout algorithm (glyph_box_layout):
- For each visible child, set
w = pref_w,h = pref_h - Position sequentially along the primary axis (x for HBox, y for VBox) with
spacingbetween children - Set cross-axis position to
padding - Track the maximum cross-axis dimension
- Remove trailing spacing, add final padding
- Set the box’s own dimensions to fit all children
Layout is triggered automatically when children are added via glyph_widget_add_child. If a child is itself a box, layout recurses.
The draw function for boxes is a no-op – box containers are invisible. Children draw themselves via the standard glyph_widget_draw_tree traversal.
Additional Widgets
The header declares additional widget types that follow the same pattern:
- Slider (
glyph_slider_t): Horizontal slider with min/max range and drag interaction - Radio Group (
glyph_radio_t): Mutually exclusive selection from up to 12 options - Separator (
glyph_separator_t): Visual divider line - Toggle Switch (
glyph_toggle_t): On/off switch with label
Drawing Primitives (draw.c)
The software rasterizer provides all rendering operations. No hardware acceleration is used.
Primitive Functions
| Function | Description |
|---|---|
draw_px |
Bounds-checked single pixel write |
draw_fill_rect |
Clamped filled rectangle |
draw_rect |
Outlined rectangle (4 edges) |
draw_gradient_v |
Vertical linear gradient |
draw_line |
Bresenham line |
draw_circle |
Midpoint circle (outline) |
draw_circle_filled |
Filled circle via horizontal spans |
draw_rounded_rect |
Filled rounded rectangle (Bresenham quarter-circles + rect fills) |
draw_blit |
Opaque pixel copy with clamp |
draw_blit_scaled |
Nearest-neighbor scaling with 16.16 fixed-point stepping |
draw_blit_keyed |
Color-keyed blit (skip pixels matching key color) |
draw_blend_rect |
Alpha-blended filled rectangle |
draw_blend_rounded_rect |
Alpha-blended rounded rectangle (no double-blend) |
draw_box_blur |
Two-pass (H+V) box blur with running sum |
draw_text |
Bitmap font text with background |
draw_text_t |
Bitmap font text, transparent background |
draw_text_center |
Centered bitmap text in a given width |
draw_text_ui |
TTF text with bitmap fallback |
Box Blur Implementation
The draw_box_blur function implements a separable two-pass box blur:
Pass 1 (horizontal): For each row, maintain a running sum over a window
of (2*radius+1) pixels. Slide the window across, dividing by span.
Edge pixels are clamped (replicated).
Pass 2 (vertical): Same algorithm applied to each column.
A static scratch buffer (uint16_t *tmp) is retained between calls to avoid per-frame allocation. This is important since draw_box_blur is called for every frosted element: top bar, dock, each frosted window, and the context menu.
Alpha Blending
draw_blend_rect performs per-pixel alpha compositing:
uint32_t or_ = (cr * alpha + pr * inv_alpha) / 255;
The draw_blend_rounded_rect function carefully avoids double-blending by decomposing the rounded rectangle into three non-overlapping rectangular strips plus four quarter-circle corner regions, each blended exactly once.
Font Rendering (font.c)
Glyph supports TTF vector fonts via stb_truetype, with automatic bitmap fallback.
Architecture
Font rendering pipeline:
+---------------------------+
| TTF file on disk |
| /usr/share/fonts/*.ttf |
+---------------------------+
| font_load()
v
+---------------------------+
| font_t |
| stbtt_fontinfo |
| raw TTF data |
+---------------------------+
| font_bake(size_px)
v
+---------------------------+
| baked_size_t |
| 512x512 alpha atlas |
| stbtt_bakedchar[95] |
| ascent, descent, height |
+---------------------------+
| font_draw_char()
v
+---------------------------+
| Alpha-blended glyph blit |
| per-pixel: fg*alpha + |
| bg*(255-alpha) |
+---------------------------+
Font Loading and Baking
void font_init(void); /* loads Inter + JetBrains Mono */
font_init attempts to load two fonts:
- Inter (
/usr/share/fonts/Inter-Regular.ttf) – UI font, pre-baked at sizes 11, 12, 13, 14, 16, 20 - JetBrains Mono (
/usr/share/fonts/JetBrainsMono-Regular.ttf) – terminal font, pre-baked at sizes 14, 16, 18, 20
Each baked size produces a 512x512 grayscale alpha atlas covering ASCII 32-126 (95 characters). Maximum 8 baked sizes per font. Partial baking is tolerated (some characters may render as empty space).
TTF-Aware Measurement
int glyph_text_width(const char *text); /* uses g_font_ui at 14px */
int glyph_text_height(void); /* line height at 14px */
int glyph_char_width(void); /* width of "M" at 14px */
These helpers are used by all widget sizing code. If the TTF font is not available, they fall back to the bitmap font constants (FONT_W = 10, FONT_H = 20).
Bitmap Font Fallback
The Terminus 10x20 bitmap font (terminus20.h) provides a complete ASCII glyph set as a compiled-in byte array. Each character is 20 rows of 16-bit bitmaps (10 significant bits per row). This is the guaranteed-available renderer when TTF fonts are missing.
Color Palette
Glyph uses a dark-mode palette with a blue tinge, defined in draw.h:
| Constant | Value | Usage |
|---|---|---|
C_BG1 |
0x001B2838 |
Desktop background |
C_BG2 |
0x000D1B2A |
Darker background variant |
C_WIN |
0x00E8E8F0 |
Primary text on dark backgrounds |
C_WIN_BG |
0x00222838 |
Widget surface / card background |
C_TEXT |
0x00E0E0E8 |
General text color |
C_ACCENT |
0x004488CC |
Accent blue (buttons, selections) |
C_SUBTLE |
0x00808898 |
Deemphasized text |
C_SHADOW |
0x00080810 |
Color key for frosted transparency |
C_TERM_FG |
0x00E0E0E0 |
Terminal foreground |
C_TERM_BG |
0x000A0A14 |
Terminal background / frosted key |
C_INPUT_BG |
0x001A2030 |
Text field / list background |
C_INPUT_BD |
0x00404860 |
Text field / list border |
C_SEL_BG |
0x00305880 |
Selection / hover highlight |
C_RED |
0x00FF5F57 |
Close button, errors |
C_GREEN |
0x0028C840 |
Maximize button, success |
C_YELLOW |
0x00FEBC2E |
Minimize button, warnings |
Build
Glyph compiles to a static library:
SRCS = draw.c widget.c window.c box.c label.c button.c textfield.c \
checkbox.c progress.c image.c scrollbar.c listview.c menubar.c \
tabs.c font.c
libglyph.a: $(OBJS)
ar rcs $@ $(OBJS)
Compiled with musl-gcc (-O2 -fno-pie -no-pie -Wall). The library is linked by both Lumen (compositor) and Bastion (display manager).
Source Files
| File | Lines | Purpose |
|---|---|---|
glyph.h |
372 | Public API: all widget types, window, geometry |
draw.h |
75 | Drawing primitive declarations, color palette |
draw.c |
556 | Software rasterizer: rects, circles, blur, blend, text |
font.h |
51 | TTF font API declarations |
font.c |
299 | stb_truetype integration, atlas baking, glyph rendering |
widget.c |
253 | Base widget tree operations: add, remove, dirty, hit test, focus |
window.c |
416 | Window: chrome rendering, dirty tracking, input dispatch |
box.c |
100 | HBox/VBox layout containers |
label.c |
80 | Static text label |
button.c |
134 | Clickable button with states |
textfield.c |
163 | Single-line text input |
checkbox.c |
88 | Toggle checkbox |
progress.c |
62 | Progress bar |
scrollbar.c |
113 | Vertical scrollbar |
listview.c |
173 | Scrollable list view |
menubar.c |
213 | Menu bar with dropdowns |
tabs.c |
156 | Tabbed container |
image.c |
34 | Raw pixel buffer display |
terminus20.h |
– | Compiled-in Terminus 10x20 bitmap font data |
stb_truetype.h |
– | Third-party TTF parser/rasterizer (header-only) |