The app loop, regions & jobs
Event-driven, idle-cheap
App runs the main loop. It is event-driven, not a busy render loop: it blocks waiting for input (or a worker wakeup()), then repaints only the regions that are dirty. An idle window does zero GPU work and zero CPU spin — the loop is woken by the OS when something actually happens.
ui::App app(*sys, *window);
app.add(std::move(menuBar)); // top-level regions, painted in order
app.add(std::move(dock));
app.setLayout([&](Rect r){ /* position regions within the window */ });
app.run();
Each frame, the loop drains all queued events, dispatches them, applies any notifications, and redraws once — so a burst of mouse-moves coalesces into a single repaint, bounding latency to a frame without doing redundant work.
Regions
The screen is divided into regions. A Region is a rectangle (in logical points) that knows how to draw, handle an event, and react to a notification (onNotify); it carries a dirty flag. Events route by hit-test to the region under the cursor, with a capture slot so a modal drag (a slider, a divider) keeps receiving moves until it finishes. A WidgetView is a region that hosts a widget tree; the docking shell is a region that tiles sub-regions.
Per-region offscreen buffers
The reason panning one panel does not repaint the whole window: each region renders to its own persistent GPU texture, and a frame only re-renders the regions whose dirty flag is set — the rest are re-composited from their cached textures. Move the viewport and only the viewport's layer is repainted; the inspector and outliner are blitted untouched. This is the single biggest contributor to the toolkit's fluidity, and it is automatic — a region opts in by being a dock editor.
Jobs and notifiers
Long work must never block the loop. Two pieces handle that:
- The job system runs work off the main thread. A worker reports progress and results through a notifier — a thread-safe "this changed, redraw later" signal — and can
wakeup()the loop so the result appears promptly. - The notifier queue decouples what changed from when we draw: a notification marks regions dirty and schedules a repaint, rather than drawing synchronously from wherever the change happened.
This is exactly how the Editor keeps the UI responsive while a render runs or an agent edits over the network: the heavy or cross-thread work posts a notifier, and the loop redraws the affected regions on its own schedule. Input arrives as raw events; the event-value model turns it into intent before your widgets ever see it.