The widget set

The retained widget tree

A widget is a node in a retained tree with two-phase layout: measure (how big do I want to be, given an available width) then arrange (here is your final rect). A WidgetView hosts the tree inside a region, lays it out, draws it, and routes events with a capture slot for modal drags and a focus slot for typing.

struct Widget {
    virtual Size measure(const Theme&, float availW) = 0;
    virtual void arrange(const Theme&, Rect);
    virtual void draw(gpu::Canvas&, const Theme&) = 0;
    virtual bool handle(const Event&) { return false; }
};

The control set

The toolkit ships a full control set, each drawn through the theme (no hardcoded colours) and acting on synthesized intent:

Construction without ceremony

Widgets are built through the Build.h factories so a tree is one readable expression: containers take their children directly, leaves configure up front, and the factories handle ownership and conversion so the call site stays clean:

auto panel = column(
    title("Transform"),
    field("Position", numberField(0.0f)),
    field("Visible",  checkbox(true)),
    row(flex(button("Cancel"), 1), flex(button("Apply"), 2)));

flex(child, weight) weights a row child; cfg(widget, fn) tweaks any extra knob in place. This parsimony is the toolkit's house style: the common case is short, and the easy way is the safe way.

Acting on intent

Momentary controls fire on a clean Click (press + release with no drag), so pressing a button and dragging away cancels it, the desktop-correct behaviour. Drags begin on the first Drag event past the threshold (a NumberField scrub, a list reorder). Because the event-value model synthesizes this uniformly, every widget agrees on what a click is. Hover, active and focused states are tracked per widget and drawn through the theme, so the whole set restyles at once when the theme changes.

A NumberField scrub reads the modifier chord the way a desktop slider does: Shift engages fine precision (the per-pixel step shrinks, smoothly and jump-free, even when you press Shift mid-drag), Ctrl snaps the value to a discrete increment, and Alt-click resets the field to its declared default — so dragging in a precise number is practical, not just an approximation.