1# The interface kit and the renderer
2
3Snowbound draws its own interface. The section tabs, toolbar, page tabs,
4sidebar, menus and command palette are all painted by the app through one GPU
5renderer, the same one that paints the page. This essay explains why, and how
6the `ui` and `draw` crates are built to make that pleasant rather than
7heroic.
8
9## The look: half OneNote, half now
10
11The shell is a deliberate homage. Section tabs lean over one another at 45°
12with a faint highlight along their tops. The open tab lies on top, in its
13section's colour, and frames the page. That colour shades gently down the
14window and becomes the accent for the whole interface, easing to the next
15section's colour when you switch. Page tabs sit on the right, and the open
16one joins the page with rounded inside corners; OneNote's Display options move
17them to the left and the notebooks to the right. It is OneNote 2010's layout,
18drawn with soft shadows, concentric corner radii and a dark appearance. Call
19it half-skeuomorphic: the shapes that made OneNote's notebook metaphor
20legible, without the 2010 chrome.
21
22The motion comes from [File Pilot](https://filepilot.tech): things move
23quickly and never feel like they're waiting on an animation. Animated values
24ease exponentially toward their targets with a short half-life. That is
25frame-rate independent, a retargeted animation continues smoothly from where
26it is, and it settles fast. Popups open and close on short timed curves,
27slow enough to follow: a menu swings out of the pointer or its button as it
28fades in, a combo's field widens into its list, and a dialog swings up into
29place near the window's top over a dimmed window, as Windows opens a window; the
30command palette swings in the same way, undimmed, as does a dialog docked inside the
31page it changes, such as the backgrounds gallery. A filtered list shows its new
32results at once. On GNOME and KDE, menus instead
33look and move as the desktop's own (see [platforms](platforms.md)). A segmented control
34slides a raised face along a track, as macOS, libadwaita and Windows 11 draw theirs, and on
35Mac OS X 10.6 and Windows 7 presses one of a row of joined buttons.
36
37## Why not native widgets
38
39- **The page has to be custom-drawn anyway.** OneNote-faithful layout needs
40 Windows line metrics, OneNote's grid, and its tag and list geometry. No
41 platform text view does that, so a renderer and a text-editing core exist
42 regardless. Drawing the chrome with them costs little more.
43- **One renderer, one pass.** The page and the chrome share a glyph atlas, an
44 image cache and a frame. A text field in the toolbar edits with exactly the
45 keys, chords, click counts and caret movement the page uses, because both go
46 through `draw::edit`.
47- **The shell is not a platform idiom.** Leaning section tabs and a
48 colour-framed page don't exist in AppKit or GTK. The app would be fighting
49 its toolkit.
50- **Portability.** The same interface runs on macOS and Linux today, and the
51 project aims further (Windows, and old versions of OS X).
52
53The platform still owns what it's best at and what people expect to be
54native. That means file pickers, alerts and date pickers (AppKit's sheets on
55macOS; the portal on Linux, which otherwise asks with the kit's own), none of
56which blocks the window, the caret and selection colours, each platform's
57editing chords, the keychain, the traffic lights and window frames. On iOS the
58split goes further (see [platforms](platforms.md)).
59
60## Immediate mode, with memory
61
62`ui` is an immediate-mode kit in the sense Casey Muratori and Ryan Fleury use
63the term. The *API* is immediate: every frame, builder code declares boxes
64from application state, and there's no widget tree to keep in sync with the
65model. The *implementation* remembers plenty:
66
67```text
68frame N
69 route input ─── against frame N-1's layout (hover, press, focus, drags, wheel)
70 build ───────── app code declares boxes; each reads its Signal (clicked, dragging, events…)
71 solve layout ── per axis: pixels · label size · fraction of an ancestor · sum of children
72 overflow taken from space, then by folding groups, then by strictness
73 paint ───────── Layers: primitives under a clip, or a Custom box the host paints
74 after paint ─── work the frame asked for (page requests, saving) runs, then asks for a frame
75```
76
77- **Identity** is a hash of the parent's id and a part the builder chooses. A
78 cache keyed by those ids keeps hover, press, focus, scroll and animation
79 state between frames.
80- **Input is answered one layout late.** Events route against the previous
81 frame's boxes before building, so a box can read what happened to it while
82 it is being declared. It sounds odd and is invisible in practice.
83- **Layout is solved after building**, per axis. A box can be sized in fixed
84 pixels, by its label, as a fraction of an ancestor, or by its children. When
85 siblings overflow, space sized from an ancestor gives way first. Then a row
86 *folds* its groups, boxes with a full and a folded form (`Spec::fold`), by
87 priority, reaching into its boxes sized by their children, so a group can
88 fold inside another. Only then do boxes give up room by their *strictness*, the least
89 strict first. That one knob covers most of what flexbox is usually needed
90 for. A box filling across a parent sized by its children stretches to what
91 its siblings make it, and a popup that isn't strict gives way to the window,
92 so a dialog is as tall as its contents up to the window and scrolls within.
93- **The toolbar is one row that never overflows.** Both forms of every group
94 are built each frame and the solver picks, so a group folds in the frame the
95 window narrows, with no widths remembered or worked out by the app. A folded
96 group is a dropdown of the same command-table rows, and a gallery it lists
97 opens beside its row as a submenu, on hover, Right or a click. The
98 section tabs scroll sideways past the row's room, fading out where cut.
99- **Nothing runs while nothing changes.** The kit reports whether it wants
100 another frame (queued input, an animation still settling) and when a timed
101 change such as a caret blink is due. The app sleeps in between.
102- **Lists are virtual for free.** `ui::list` builds only the rows in view from
103 the source data, and holds its place on the selection as items arrive and
104 leave above it. No separate model sits between the data and the rows.
105
106The page is a **custom box**. `ui` routes it the pointer, wheel, key and
107input-method events that land on it, in order. The host hands them to
108`canvas`'s `PageView` and paints the page into a clipped layer of the same
109frame. The page's scrollbars are ordinary `ui` widgets; the canvas only
110reports its scroll bounds.
111
112`ui::popup` builds menus, filterable lists, a colour grid, galleries and a fuzzy command
113palette on a popup layer that takes input above everything else. `ui::shell`
114has the OneNote-specific controls: section tabs and compact toolbar buttons.
115The kit doesn't know what a notebook is. `snowbound` assembles the window from
116these parts.
117
118## Accessibility and the keyboard
119
120The interface presents itself to assistive technology as an AccessKit tree
121built from the same boxes as the frame. A box with a `Spec::role` is a node;
122the kit's widgets choose their own, and `Ui::access` adds states, values and
123names to any box. A box without a role lets its children through, and its
124text reads as a label.
125
126- **Names come from what a sighted user reads.** A control without one is
127 named by its text and its children's, or by its tooltip: the title names
128 it, the chord becomes its shortcut, and the description its description.
129 An icon button's command-table title is its name, and a split button's arrow
130 is that name with "Options", so no two toolbar controls share one.
131- **Popups are menus and dialogs.** A menu's highlighted row is the focus
132 within it, so arrows read as they move; a palette is a dialog whose field
133 keeps the focus while its list's selection moves. Opening a popup moves the
134 focus into it, and closing it gives the focus back. A command menu opens at
135 its top with nothing highlighted; a value picker (fonts, sizes, a combo's
136 list) opens on its current value.
137- **Actions are input.** A press or a new value from assistive technology
138 arrives as an event and is answered next frame, as a click would be.
139- **Only changes are sent**, and only while something listens.
140
141The page keeps its own tree (see [canvas](canvas.md#accessibility)). The
142host grafts it at the page's box, sending the interface's tree first, since
143it holds the graft, then the page's.
144
145```text
146Window
147├─ Toolbar buttons named by their tooltips, combos with their values
148├─ TabList section tabs
149├─ Group "Page" the page's own tree, grafted
150├─ TabList page tabs
151└─ Menu | Dialog open popups
152```
153
154The keyboard reaches the same controls. Tab steps through them, within an
155open popup or across the window; a toolbar, tab list or tree is one step,
156entered at its selected control, and arrows move within it. F6 steps between
157those groups and the page, as Windows and GTK step between panes, and on
158macOS Control-F5 goes to the toolbar. Space or Enter presses, Escape closes a
159popup or returns the focus to where the keyboard took it from, and the
160focused control wears a ring until the pointer is used. The page keeps Tab
161for itself, so F6 leaves it.
162
163## `draw`: the renderer
164
165`draw` is a small renderer submitting through wgpu, or Direct3D 11, OpenGL 2.1
166or a browser's 2D canvas where wgpu can't; the backends share everything but
167their device layer. A frame is a list of layers, each with its own transform and
168clip. A layer is a flat sequence of primitives: glyph runs,
169SVG icons (tinted or in their own colours), SVG paths filled or stroked,
170rounded or gradient rectangles, pen strokes and raster images.
171
172- **Glyphs** are rasterized with Swash into an atlas, at quarter-pixel phases,
173 so text placed at fractional positions stays crisp and doesn't shimmer as it
174 moves. Icons and paths rasterize once per size and phase into the same
175 atlas. A rounded box's soft shadow is worked out in the shader instead, so
176 popups of any size, widening or not, cost the atlas nothing.
177- **The renderer never measures text.** Text reaches it through a trait that
178 visits positioned glyph runs. Line metrics stay with whoever laid the text
179 out, which is how the page keeps its Windows metrics while the chrome uses
180 ordinary ones.
181- **Batches** merge consecutive primitives that share a texture and clip,
182 without reordering paint.
183- **A popup in motion is one picture.** Layers sharing a motion draw
184 offscreen together, then fade and lean back as a whole, so nothing beneath
185 or within shows through a row. A popup's contents clip to its rounded
186 outline, and one opening over a combo lays its rows out at their final
187 width while only the outline widens.
188- **Caches are bounded.** The image cache has a fixed budget. A full glyph
189 atlas is cleared and rebuilt with only what the current frame needs; where
190 one frame alone needs more than half of it, it doubles, up to what the GPU
191 allows. Images are filtered in linear light with premultiplied alpha.
192
193GPU readback tests pin the rasterization down: successive quarter-pixel
194translations have to move the ink's centroid in quarter-pixel steps, and
195repaint, cache eviction and a new renderer must all produce identical pixels.
196
197## Testing an interface you can't click
198
199A covered window gets no redraws, so interaction is scripted.
200`SNOWBOUND_REPLAY` feeds the app a file of pointer, key, wait and snapshot
201steps, and `accessibility` steps that write the whole window's tree as text, so a
202hidden window's tree can be checked without a screen reader. `--screenshot` draws the whole window offscreen in each appearance
203(the README's screenshots are made this way). `ui`'s own tests build frames
204headlessly and assert on layout, routing and signals.