GUI provides a simple cross-platform middleware layer for lightweight window work.
Backends:
- Windows via
windows(Win32) - Linux via
linux(X11) - Web/JS runtimes with a Canvas/WebGL command middleware state
This library is intentionally small and focuses on a consistent API shape.
GUI internals are split into focused modules that can also be imported directly:
Because Magnolia does not currently expose direct DOM built-ins, the web backend
uses a command queue model. You can optionally pass options.webBridge to
createWindow(...) to forward recorded Canvas/WebGL operations to host-side
JavaScript.
The GUI renderer includes several performance optimizations for 2D and 3D rendering. See gui-performance.md for:
- Projection parameter caching (40–50% reduction in vertex overhead)
- Deferred lighting for culled faces (20–30% faster backface culling)
- Renderer mesh caching (100% bypass of repeated allocations)
- Circle outline trig optimization (95% reduction in per-segment trigonometry)
- Benchmarking guide and best practices
Expected overall improvement: 5–15% FPS gain on typical hardware.
gui := import('GUI')
// alias also supported:
gui := import('gui')
Returns one of these atoms:
:windows:linux:web:unknown
Boolean helpers derived from backend().
Builds a packed RGB integer color value.
Builds a packed RGB color by composing an RGBA-style input over
background using alpha a in [0, 1].
This is equivalent to opacity(rgb(r, g, b), a, background).
Pre-composes color over background using an opacity in [0, 1] and
returns a packed RGB integer that can be passed to the native drawing helpers.
This is useful on native backends that currently draw with opaque packed colors instead of full RGBA surfaces.
Creates a window state object.
Useful options fields:
frameMs- target idle frame step in milliseconds (default:16)maxFrameDtMs- clamps per-framedtafter stalls (default:250)updateOnDispatch- whether input/message dispatch should also triggeronFrame(default:false)className(Windows) - override the Win32 class name used during registration. Defaults to a generated unique class name.dllLoadMode(Windows) - DLL probe strategy for renderer capability checks:sync(default) probes duringcreateWindow;asyncstarts probing in the background so window creation stays responsive.- Windows icon options:
icon- base icon spec used for both big/small icon when specific values are not providedtaskbarIconoriconBig- big icon (taskbar/alt-tab)windowIconoriconSmall- small icon (titlebar)
Icon spec can be:
-
an integer resource ID (for example
32512forIDI_APPLICATION) -
a string path to an
.icofile -
an object:
{ id: <int> },{ path: <string> }, or{ handle: <int> } -
Windows: registers a default class and creates a top-level window.
-
Linux: creates a default X11 window.
-
Web: creates logical state with no native host window API calls.
Returns:
{type: :ok, ...windowState}on success{type: :error, error: <string>, detail: <any>}on failure
Shows/maps the window where supported.
Hides/unmaps the window where supported.
Moves the window top-left position where supported.
- Windows: uses Win32
SetWindowPoswithout resizing. - Linux: uses X11
XMoveWindow. - Web: records
:window_movemiddleware op and updates window state.
Resizes the window where supported.
- Windows: uses Win32
SetWindowPoswith current position. - Linux: uses X11
XResizeWindow. - Web: updates middleware state and records
:window_resize.
Scales the current window size by multipliers and applies resize(...).
scaleYis optional; when omitted it usesscaleX.- Output dimensions are clamped to at least
1x1.
Toggles fullscreen-like behavior (default enabled = true).
- Windows: maximize/restore via
ShowWindow. - Linux: best-effort fullscreen by moving to
(0,0)and resizing to display dimensions. - Web: updates middleware fullscreen state and records
:window_fullscreen.
Sets the title where supported.
Optional frame batching helpers.
- On Windows, these reuse one device context (DC) across multiple draw calls and flush/release once per frame, which reduces flicker and intermittent draw stalls.
- Windows layer selection options are available via
createWindow(..., options):options.layer2D:auto(default),vulkan,opengl,ddraw,gdioptions.vulkanAuto: allowautoto select Vulkan when available (defaultfalse)options.dllLoadMode:sync(default) orasyncfor background DLL probingoptions.layer3D:auto(default),d3d9,cpu,noneSelected capabilities are exposed onwindow.layerswhen using the Windows backend. Vulkan selection currently bootstraps Vulkan instance/surface state but still uses a stable fallback presenter for frame display.
- On Linux/Web they are safe no-ops for API consistency.
Closes/release window resources.
- Windows: destroys the window and unlocks the pinned thread.
- Linux: destroys window and closes display.
- Web: marks the middleware state as closed.
Web backend windows include these fields:
window.canvaswindow.webglwindow.messages(recorded middleware operations)
GL_COLOR_BUFFER_BITGL_DEPTH_BUFFER_BITGL_TRIANGLES
Configures canvas metadata (id, width, height, dpr) and records
:canvas_create.
Initializes WebGL middleware context metadata and records :webgl_init.
Records shader creation and returns {type: :ok, shader: {...}}.
Records program creation and returns {type: :ok, program: {...}}.
Sets the active program and records :webgl_use_program.
Sets clear color state and records :webgl_clear_color.
Sets viewport state and records :webgl_viewport.
Records :webgl_clear (default mask is GL_COLOR_BUFFER_BIT).
Records :webgl_draw_arrays (default mode is GL_TRIANGLES).
Records :webgl_flush and returns queued commands.
Pumps one backend event step and returns an event object.
Common result shapes:
{type: :dispatch, ...}{type: :idle}{type: :closed}{type: :error, ...}
Runs a simple loop:
- calls
poll(window) - dispatches
onEvent(window, evt)on:dispatch - calls
onFrame(window, dt)when a frame is due on:dispatchand:idle - applies
frameMspacing and clampsdttomaxFrameDtMsafter long stalls - on Windows, marks resize/paint-related dispatches as urgent so the next frame is not delayed
- exits on
:closed
Each :ok window now carries a lazily initialized event bus at window.eventBus.
The GUI module exposes thin helpers around this bus:
eventBus(window)returns the bus instanceon(window, event, handler)subscribes and returns a tokenonce(window, event, handler)one-shot subscription, returns a tokenoff(window, event, tokenOrHandler)unsubscribes and returns removed countemit(window, event, payload, onDone?)emits sync/async based on callback presencelistenerCount(window, event)returns active listener countclearListeners(window, event?)clears one event or all listenersonDispatch(window, fn(step){...})convenience wrapper overon(window, :dispatch, ...)onceDispatch(window, fn(step){...})one-shot dispatch subscriptiononKeyDownEvent(window, fn(key, evt){...})filters dispatch events to:keyDownonKeyUpEvent(window, fn(key, evt){...})filters dispatch events to:keyUponceKeyDownEvent(window, fn(key, evt){...})one-shot:keyDownsubscriptiononceKeyUpEvent(window, fn(key, evt){...})one-shot:keyUpsubscription
Lifecycle aliases:
onRunStart/onceRunStart(window, handler)for:runStartonIdle/onceIdle(window, handler)for:idleonFrame/onceFrame(window, handler)for:frameonClosing/onceClosing(window, handler)for:closingonClosed/onceClosed(window, handler)for:closed
Built-in events emitted by GUI runtime:
:runStartwhenrun(...)begins on a window:dispatchfor each polled dispatch step:idlefor each idle step:framewheneveronFrame(window, dt)is invoked (payload includesdtandtimeNs):closingwhenclose(window)begins:closedafterclose(window)completes or when run loop receives closed step
For native Windows message handling, GUI exposes typed subscribers that wrap
on(window, :dispatch, ...) and filter by message type for you.
onMouseMove(window, fn(mx, my) {...})onLButtonDown(window, fn(mx, my) {...})onLButtonUp(window, fn(mx, my) {...})onRButtonDown(window, fn(mx, my) {...})onRButtonUp(window, fn(mx, my) {...})onKeyDown(window, fn(vk) {...})onKeyUp(window, fn(vk) {...})onChar(window, fn(code) {...})onResize(window, fn(width, height) {...})
Each helper returns the same subscription token as on(...), so you can remove
handlers with off(window, :dispatch, token).
Draws text in the target window.
Use setFont(window, fontSpec) to control font family/size/weight used by
subsequent text draws.
When color is omitted, GUI uses its default light text color. You can pass a
packed RGB color from rgb(...) or a pre-composed color from
opacity(...).
- Windows:
TextOutW - Linux:
XDrawString - Web: records a logical
:textdraw op inwindow.messages(includesfontCSS value when set)
Sets the active text font for a window.
fontSpec fields:
family- font family name (default:Segoe UIon Windows)size- font size in px (default:16)weight- numeric weight (default:400, bold is usually700)italic- booleanunderline- booleanstrikeOut- booleancss- optional web CSS font shorthand override (web backend only)
Example:
gui.setFont(window, {
family: 'Consolas'
size: 18
weight: 700
})
gui.drawText(window, 24, 32, 'Font-aware text', gui.rgb(248, 232, 242))
Clears the custom font and returns to backend defaults.
Fills a rectangle.
- Windows: GDI brush + rectangle draw
- Linux: X11 foreground +
XFillRectangle - Web: records a logical
:rectdraw op inwindow.messages
Draws a line segment in the target window.
- Windows: GDI pen +
MoveToEx/LineTo - Linux: X11
XDrawLine - Web: records a logical
:linedraw op inwindow.messages
GUI now includes lightweight graph drawing helpers built on top of
drawLine(...), fillRect(...), and drawText(...).
Computes a display range for numeric series.
options.minoptional fixed minimumoptions.maxoptional fixed maximumoptions.paddingoptional padding applied to both sides
Returns {min, max, span}.
Maps a sample index to an X coordinate in a graph rect.
Maps a numeric value to a Y coordinate in a graph rect using a
graphRange(...) result.
Draws graph background, grid, and border axes.
options.gridColoroptions.axisColoroptions.backgroundColoroptions.xTicks(default5)options.yTicks(default4)options.showGrid(defaulttrue)
Draws a line graph with optional points and min/max labels.
options.lineColoroptions.pointColoroptions.showPoints(defaulttrue)options.showLabels(defaulttrue)options.min,options.max,options.rangePaddingoptions.axisnested options forwarded todrawGraphAxes(...)
Draws a bar graph.
options.barColoroptions.barBorderColoroptions.barGap(default2)options.showLabels(defaulttrue)options.min,options.max,options.rangePaddingoptions.axisnested options forwarded todrawGraphAxes(...)
Draws a compact line-only graph for dashboards and small stat cards.
options.lineColoroptions.backgroundColoroptions.min,options.max,options.rangePadding
GUI now includes a 2D helper suite for math, transforms, camera mapping, and shape drawing.
Vec2(x, y)Rect2(x, y, width, height)vec2Add(a, b)vec2Sub(a, b)vec2Scale(v, s)vec2Dot(a, b)vec2Len(v)vec2Normalize(v)rectTranslate(rect, dx, dy)rectContains(rect, point)rectIntersects(a, b)
Transform2D(options)fields:tx,tytranslationrrotation in degreessx,syscale (orscalefor uniform)
applyTransform2D(point, transform)Camera2D(options)fields:x,y,zoomworldToScreen2D(point, camera, window)screenToWorld2D(point, camera, window)
drawRect2D(window, x, y, width, height, color?, filled?)drawCircle2D(window, cx, cy, radius, color?, filled?)drawPolyline2D(window, points, color?, closed?)drawPolygon2D(window, points, color?, filled?)drawGrid2D(window, spacing?, color?, originX?, originY?)
GUI includes a lightweight wireframe 3D renderer that works on native backends and uses command recording on web backends.
Constructs a 3D vector object.
Returns a cube wireframe mesh object:
verticeslistedgesindex pairs
Constructs a custom wireframe mesh object.
Builds an XZ-plane wireframe grid mesh.
Builds an XYZ axis wireframe mesh.
Builds a wireframe mesh from voxel center positions.
voxels: list of objects like{x: 0, y: 1, z: 2}voxelSize: cube size per voxel (default1)
Mutable voxel set helper.
Options:
voxelSizevoxels(initial list)
Methods:
set(x, y, z, value?)get(x, y, z)clear()voxels()toMesh()
Projects and draws mesh edges as 2D lines.
Fills a projected triangle using scanline rasterization.
Renders solid faces (triangulated quads) with a simple painter sort by depth.
Transform fields:
tx,ty,tzrx,ry,rz(degrees)scale
Camera fields:
z(distance offset)fov(degrees)mode('perspective'or'orthographic')orthoScale(orthographic zoom scale)backfaceCulling(defaulttrue)
Lighting fields (for solid rendering):
x,y,z(light direction)ambient(default0.22)diffuse(default0.78)
Creates a convenience renderer object.
Options:
camerabackgroundlineColor
Returns object methods:
setCamera(camera)setLight(light)setProjection(mode, orthoScale?)clear()renderMeshSolid(mesh, transform?, color?)renderCubeSolid(size?, transform?, color?)renderMesh(mesh, transform?, color?)renderCube(size?, transform?, color?)renderGrid(size?, step?, transform?, color?)renderAxes(length?, transform?)renderVoxels(voxels, voxelSize?, transform?, color?)renderVoxelGrid(grid, transform?, color?)
gui := import('GUI')
window := gui.createWindow('Magnolia GUI', 840, 520)
if window.type = :ok {
gui.show(window)
bg := gui.rgb(12, 18, 30)
gui.fillRect(window, 0, 0, window.width, window.height, bg)
gui.fillRect(window, 24, 48, 320, 180, gui.rgba(46, 120, 226, 0.9, bg))
gui.drawText(window, 24, 26, 'Hello from Magnolia GUI')
gui.drawText(window, 24, 250, 'Close the window to exit.', gui.rgba(248, 232, 242, 0.75, bg))
gui.run(window, fn(win, evt) {
// inspect events if needed
}, fn(win, dt) {
// optional frame callback with delta time in seconds
})
gui.close(window)
}
gui := import('GUI')
window := gui.createWindow('WebGL Sample', 960, 540, {
canvasId: 'app-canvas'
})
if window.type = :ok & gui.isWeb?() -> {
gui.createCanvas(window, 'app-canvas', {
width: 960
height: 540
dpr: 1
})
gui.initWebGL(window, 'webgl', {
alpha: true
antialias: true
depth: true
})
vert := gui.webglCreateShader(window, :vertex, 'attribute vec2 aPos; void main(){ gl_Position = vec4(aPos, 0.0, 1.0); }')
frag := gui.webglCreateShader(window, :fragment, 'precision mediump float; void main(){ gl_FragColor = vec4(0.08, 0.52, 0.95, 1.0); }')
prog := gui.webglCreateProgram(window, vert.shader, frag.shader)
gui.webglUseProgram(window, prog.program)
gui.webglViewport(window, 0, 0, window.width, window.height)
gui.webglClearColor(window, 0.02, 0.03, 0.08, 1.0)
gui.webglClear(window, gui.GL_COLOR_BUFFER_BIT | gui.GL_DEPTH_BUFFER_BIT)
gui.webglDrawArrays(window, gui.GL_TRIANGLES, 0, 3)
queued := gui.webglFlush(window)
println(string(queued))
}
gui := import('GUI')
window := gui.createWindow('GUI Logging Sample', 860, 460, {
frameMs: 16
layer2D: 'auto'
})
if window.type = :ok {
state := { tick: 0, entries: [] }
gui.show(window)
gui.run(window, fn(_, evt) {
if evt.type = :dispatch & (state.tick % 120 = 0) ->
state.entries <- state.entries << ('EVENT tick=' + string(state.tick))
}, fn(win, _dt) {
state.tick <- state.tick + 1
if state.tick % 90 = 0 ->
state.entries <- state.entries << ('TRACE frame=' + string(state.tick))
if gui.beginFrame(win).type = :ok -> {
gui.fillRect(win, 0, 0, win.width, win.height, gui.rgb(18, 22, 34))
gui.drawText(win, 20, 24, 'GUI Logging Sample')
gui.drawText(win, 20, 52, 'entries=' + string(len(state.entries)))
gui.endFrame(win)
}
})
gui.close(window)
}
See samples/gui-logging.oak for a full runnable version with stdout logging.
{ new: new } := import('test')
fn formatLogLine(ts, level, message) '[' + string(ts) + '] ' + level + ' ' + message
fn shouldLogTick?(tick, every) (every > 0) & (tick % every = 0)
suite := new('GUI Logging Sample Tests')
suite.eq('format line', formatLogLine(42, 'INFO', 'ready'), '[42] INFO ready')
suite.eq('tick schedule', [shouldLogTick?(90, 90), shouldLogTick?(91, 90)], [true, false])
suite.report()
suite.exit()
See samples/test_gui_logging.oak for the complete test sample.
The GUI middleware is implemented across several focused modules. See the module pages for API details and examples.
- gui-2d — 2D math, transforms, camera mapping, and 2D drawing helpers.
- gui-3dmath — 3D math, vector/rotation utilities and projections.
- gui-draw — Cross-platform drawing primitives (text, lines, rects).
- gui-mesh — Mesh/voxel builders and helpers used by the renderer.
- gui-render — High-level 3D renderer and
Renderer3Dconvenience API. - gui-raster — Scanline rasterization, triangle fill, and culling.
- gui-native-win — Win32-specific window lifecycle and helpers.
- gui-native-linux — X11-specific window lifecycle and helpers.
- gui-web — Canvas/WebGL middleware and command-queue helpers for web runtimes.
samples/gui-sample.oak- cross-platform GUI quickstart; on Windows it also displays the requested 2D layer, active presenter, and fallback statesamples/gui-game.oak- bouncing-box mini game using GUI middleware with frame-rate independent motion and Windows presenter-state overlaysamples/gui-logging.oak- GUI logging sample with frame/event logs rendered in-window and mirrored to stdoutsamples/test_gui_logging.oak- focused test sample for GUI logging helper functions using thetestlibrarysamples/gui-3d.oak- rotating wireframe cube using GUI 3D renderersamples/windows-higher-renderers.oak- Windows renderer probe for Vulkan/OpenGL/DDraw/D3D capability and staged fallback metadatasamples/windows-2d-layer-hotload.oak- Windows-only hotload demo that recreates the window every 2 seconds to cyclegdi,ddraw,opengl, andvulkanrequests while preserving one animated scenesamples/windows-2d-layer-hotload-game.oak- Windows-only hotload demo that recreates the window every 2 seconds while preserving the bouncing-box game scene acrossgdi,ddraw,opengl, andvulkanrequests
import('GUI')remains the primary API and is backward compatible.- Mesh and voxel builders are factored into
import('gui-mesh')internally. - The renderer factory is factored into
import('gui-render')internally. - Transform/projection math is factored into
import('gui-3dmath')internally. - Shading, culling, and raster routines are factored into
import('gui-raster')internally. - Web Canvas/WebGL middleware queue logic is factored into
import('gui-web')internally. - Win32 lifecycle/frame internals are factored into
import('gui-native-win')internally. - Linux lifecycle/event-loop internals are factored into
import('gui-native-linux')internally. - Cross-platform drawing primitives are factored into
import('gui-draw')internally. - 2D math/shape tooling is factored into
import('gui-2d')internally. - Optional direct mesh module imports:
Mesh(vertices, edges)GridMesh(size, step)AxesMesh(length)VoxelMesh(voxels, voxelSize)VoxelGrid(options)
- Optional direct renderer module import:
Renderer3D(deps, window, options)
- Optional direct 3D math module imports:
degToRad(deg)Vec3(x, y, z)transformPoint(v, transform)projectPoint(window, p, camera)transformVertices(vertices, transform, i, out)
- Optional direct raster module imports:
drawTriangleFilled(deps, window, p0, p1, p2, color)drawMeshSolid(deps, window, mesh, transform, camera, color, light)drawMeshWireframe(deps, window, mesh, transform, camera, color)
- Optional direct web module imports:
createWindowState(title, width, height, frameMs, updateOnDispatch, options)createCanvas(window, id, options)initWebGL(window, contextName, attrs)webglCreateShader(window, shaderType, source)webglCreateProgram(window, vertexShader, fragmentShader)webglUseProgram(window, program)webglClearColor(window, r, g, b, a)webglViewport(window, x, y, width, height)webglClear(window, mask, colorBufferBit)webglDrawArrays(window, mode, first, count, trianglesMode)webglFlush(window)