Concepts
Architecture #
Knots keeps UI construction separate from windows and graphics APIs. The bundled app shell is one host of the UI. It is not the only way to use the library.
Renderer when Knots owns presentation, or Painter when your host owns GPU passes.The layers #
| Layer | Responsibility |
|---|---|
ui |
Components, the layout engine, the style engine, input handling, widget state, the accessibility snapshot, and effects. |
text |
Font parsing, shaping, wrapping, and the glyph curve data. |
render.Packet |
The output of a frame. It is independent of the graphics API: vertices, glyph instances, clips, images, backdrops, and custom paint callbacks. |
renderer.Painter |
Prepares a packet for the bundled GPU backend and records it into render passes that the host owns. |
renderer.Renderer |
Owns a surface and draws packets with a Painter. |
gpu |
A small GPU interface, with a WebGPU and a Vulkan implementation. |
window |
Native windows: Cocoa, Win32, Wayland, and a browser canvas. |
knots.App |
The app shell. It owns the windows, the event loop, the renderers, async completions, and accessibility adapters. |
Frame lifecycle #
Each frame has the same steps, with or without App:
- The host collects input into an
input.FrameInput. ui.Context.beginFrame(input)returns aui.Frame.- Application code emits components into the frame.
-
ui.Context.endFrame(&frame)calculates layout, resolves styles, and builds the packet. It returns aFrame.Output. - The host applies the effects and draws the packet.
var frame = try context.beginFrame(host_input);
defer frame.deinit();
try buildUi(&frame);
const output = try context.endFrame(&frame);
Frame.Output holds the packet, the effects
(cursor_shape, clipboard_write, close,
redraw, text_input, capture_pointer,
capture_keyboard), and the accessibility snapshot. The data
is borrowed. It is valid until the next beginFrame.
If your code returns an error during a frame, call abortFrame. Then the
next beginFrame works. Frame.deinit also aborts a frame that
did not end.
The app shell #
App runs the same lifecycle for each window. It is not a second UI API. For
each window it:
- Waits for events, then collects input.
- Runs async completions for the window.
- Calls your frame callback with a
Viewand aui.Frame. - Applies the effects to the native window.
- Draws the packet with its
Renderer. - Sends the accessibility snapshot to the platform.
Read Windows and async work for the App
API.
The render packet #
A render.Packet uses its own conventions for geometry, glyphs, clips, and
shaders. It does not depend on a graphics API. The bundled Painter reads
it, and a custom renderer can read it too. A packet can contain extension commands, for
example the paint callbacks of GPUCanvas. A renderer must check the
extensions and reject commands that it does not support.
Embedding #
A program that already has a window or a GPU renderer can skip App. It
uses ui.Context directly and draws with Painter or its own
renderer. Read Embedding.
The HMR boundary #
With hot reloading, the host stays loaded. Modules are separate WASM binaries. A module gets input and bound state, and returns a serialized packet for its region. The host composes the packet into its own frame. The host owns all platform and GPU resources, so a reload does not restart the window or the GPU state. Read Hot reloading.