Skip to main content

kui_core/runtime/
builder.rs

1//! The frame builder: what a view calls between `begin_frame` and
2//! `finish_frame` to declare the tree — open, close, text, editors,
3//! images — with each spec eased against its transitions and keyframes
4//! as it is opened, and the hover / press queries a view styles by.
5
6use super::*;
7
8use crate::schema::{Identity, PropsOut};
9use crate::slots::one;
10
11/// What a node opened through [`Core::open_from`] holds: a box (left open
12/// for its children), a fragment (likewise), a `cells` grid or a stroke
13/// (leaves, closed by the door).
14pub enum Content<'a> {
15    Box,
16    /// A fragment: the function (and its image, if any) and the params.
17    Fragment(crate::fragment::FragmentRef, &'a [f32]),
18    Cells(&'a crate::cells::CellGrid<'a>),
19    Line(&'a [Vec2], Stroke),
20    /// A filled polygon; the fill is the spec's `bg`.
21    Polygon(&'a [Vec2]),
22    /// A path: its ops, the fill rule, and a stroke if it has one. The
23    /// fill is the spec's `bg`.
24    Path(
25        &'a [crate::path::PathOp],
26        crate::path::FillRule,
27        Option<Stroke>,
28        Option<crate::path::Turn>,
29    ),
30    /// The same from a `d` string, parsed here by the one parser; one
31    /// that does not parse raises `path-malformed` under the node's key.
32    PathD(
33        &'a str,
34        crate::path::FillRule,
35        Option<Stroke>,
36        Option<crate::path::Turn>,
37    ),
38}
39
40/// A node's keyframes flattened per slot for `ease_spec`, built once per
41/// node per frame (only for nodes that declare keyframes).
42struct Tracks {
43    width: Option<Vec<(f32, [f32; 4])>>,
44    height: Option<Vec<(f32, [f32; 4])>>,
45    bg: Option<Vec<(f32, [f32; 4])>>,
46    radius: Option<Vec<(f32, [f32; 4])>>,
47    opacity: Option<Vec<(f32, [f32; 4])>>,
48}
49
50impl Tracks {
51    fn of(spec: &NodeSpec) -> Self {
52        let frames = &spec.anim().keyframes;
53        let offsets = keyframes::offsets(frames);
54        // Each slot's stops in lanes, the node's own value at the ends the
55        // stops do not reach; a sizing with no amount (`fit`) has no track.
56        let track = |slot: Slot, base: Option<[f32; 4]>| {
57            keyframes::track(frames, &offsets, base?, |k| k.lanes(slot))
58        };
59        Tracks {
60            width: track(Slot::Width, spec.layout.width.amount().map(one)),
61            height: track(Slot::Height, spec.layout.height.amount().map(one)),
62            bg: track(Slot::Bg, Some(spec.style.bg.lanes())),
63            radius: track(Slot::Radius, Some(spec.style.radius)),
64            opacity: track(Slot::Opacity, Some(one(spec.style.opacity))),
65        }
66    }
67
68    fn get(&self, slot: Slot) -> Option<&Track> {
69        match slot {
70            Slot::Width => self.width.as_deref(),
71            Slot::Height => self.height.as_deref(),
72            Slot::Bg => self.bg.as_deref(),
73            Slot::Radius => self.radius.as_deref(),
74            Slot::Opacity => self.opacity.as_deref(),
75            // Keyframing a shadow would need stops for four more numbers
76            // and a color; a shadow tweens with `transition` and no more.
77            Slot::Border | Slot::Pos | Slot::Shadow | Slot::ShadowColor => None,
78        }
79    }
80}
81
82impl Core {
83    // -- Frame builder ------------------------------------------------------
84    // Flat, non-panicking, callable through FFI. Misuse (close past the root,
85    // building outside a frame) is ignored rather than UB or panic.
86
87    /// Tags subsequently created nodes with an origin (set by the runner
88    /// before handing the frame to an extension).
89    pub fn set_origin(&mut self, origin: OriginId) {
90        self.origin = origin;
91    }
92
93    /// Replaces the implicit root's spec (e.g. to make the top level a row).
94    /// Root sizing is resolved against the viewport regardless.
95    pub fn configure_root(&mut self, mut spec: NodeSpec) {
96        if let Some(app) = self.dt_app {
97            // The host's tree is wrapped for the devtools (ADR 0024,
98            // decision 2): the spec is split between the two nodes.
99            self.devtools_configure_root(app, spec);
100            return;
101        }
102        if !self.tree.is_empty() {
103            self.ease_spec(Key::ROOT, &mut spec);
104            self.tree.note(&spec, &NodeContent::Container);
105            self.tree.specs[0] = spec;
106        }
107    }
108
109    /// Replaces a transitioning node's animatable values with this frame's
110    /// eased ones. Nodes without a transition cost one branch — inlined at
111    /// the call site, so it is a branch and not a call that returns.
112    /// A slot the node's keyframes name is sampled from its cycle instead
113    /// of tweened.
114    #[inline]
115    pub(super) fn ease_spec(&mut self, key: Key, spec: &mut NodeSpec) {
116        if let Some(t) = spec.transition {
117            self.ease_transitioning(key, spec, t);
118        }
119    }
120
121    #[inline(never)]
122    fn ease_transitioning(&mut self, key: Key, spec: &mut NodeSpec, t: crate::anim::Transition) {
123        // One lookup for the whole node. Every slot below used to reach
124        // `AnimStore` by key on its own, which was seven to nine hashes and
125        // probes of the same entry per transitioning node, per frame.
126        let mut anim = self.anim.node(key);
127        let tracks = (!spec.anim().keyframes.is_empty()).then(|| Tracks::of(spec));
128        let track = |slot: Slot| tracks.as_ref().and_then(|k| k.get(slot));
129        let enter = spec.anim().enter.unwrap_or_default();
130        // Each slot: sampled from its track when keyframed, else tweened
131        // toward its declared value from where the entrance says it starts.
132        let mut ease = |slot: Slot, target: [f32; 4]| match track(slot) {
133            Some(track) => anim.sample(track, t).unwrap_or(target),
134            None => anim.drive(slot, enter.lanes(slot), target, t, true),
135        };
136        let mut sizing = |slot: Slot, s: Sizing| match s.amount() {
137            Some(v) => s.with_amount(ease(slot, one(v))[0]),
138            None => s,
139        };
140        spec.layout.width = sizing(Slot::Width, spec.layout.width);
141        spec.layout.height = sizing(Slot::Height, spec.layout.height);
142        let mut color = |slot: Slot, c: Color| Color::from_lanes(ease(slot, c.lanes()));
143        spec.style.bg = color(Slot::Bg, spec.style.bg);
144        spec.style.border_color = color(Slot::Border, spec.style.border_color);
145        spec.style.shadow.color = color(Slot::ShadowColor, spec.style.shadow.color);
146        let sh = spec.style.shadow;
147        let geom = ease(Slot::Shadow, [sh.dx, sh.dy, sh.blur, sh.spread]);
148        spec.style.shadow.dx = geom[0];
149        spec.style.shadow.dy = geom[1];
150        spec.style.shadow.blur = geom[2].max(0.0);
151        spec.style.shadow.spread = geom[3];
152        spec.style.opacity = ease(Slot::Opacity, one(spec.style.opacity))[0].clamp(0.0, 1.0);
153        spec.style.radius = ease(Slot::Radius, spec.style.radius);
154    }
155
156    /// The root node's key — for hover/press queries or `set_key_focus` when
157    /// the root itself declares the interaction (e.g. a root-level key sink).
158    pub fn root_key(&self) -> Key {
159        self.tree.keys.first().copied().unwrap_or(Key(0))
160    }
161
162    fn current(&self) -> u32 {
163        self.stack.last().copied().unwrap_or(0)
164    }
165
166    /// The key a child of the current node is derived from: the node's own
167    /// key, except inside a slot fill at the depth the fill began, where it
168    /// is the fill's namespace (`Core::fill`). One
169    /// compare on the auto-key path; `ns_depth` is `usize::MAX` outside a
170    /// fill.
171    #[inline]
172    pub(crate) fn parent_key(&self) -> Key {
173        if self.stack.len() == self.ns_depth {
174            self.ns_key
175        } else {
176            // Outside a frame — or in the devtools' own window, where the
177            // root is deferred (ADR 0024, decision 6) — there is no node
178            // to derive from, and the open that follows is a no-op.
179            self.tree
180                .keys
181                .get(self.current() as usize)
182                .copied()
183                .unwrap_or(Key::ROOT)
184        }
185    }
186
187    #[inline]
188    pub(crate) fn auto_key(&mut self) -> Key {
189        let parent = self.parent_key();
190        let i = self.counters.last().copied().unwrap_or(0);
191        if let Some(c) = self.counters.last_mut() {
192            *c += 1;
193        }
194        parent.index(i)
195    }
196
197    /// The key a child labeled `label` would get — usable before creating it,
198    /// e.g. to check hover state for styling.
199    pub fn child_key(&self, label: &str) -> Key {
200        self.parent_key().str(label)
201    }
202
203    /// The key the `i`th child gets from auto-keying — what `open_indexed`
204    /// opens with, usable before the node exists.
205    pub fn child_key_indexed(&self, i: u64) -> Key {
206        self.parent_key().index(i)
207    }
208
209    pub fn is_hovered(&self, key: Key) -> bool {
210        self.interaction.is_hovered(key)
211    }
212
213    /// Whether files dragged in from the OS are over `key`.
214    pub fn is_drop_target(&self, key: Key) -> bool {
215        self.interaction.is_drop_target(key)
216    }
217
218    /// The zone the dragged files are over, if any — what a driver
219    /// answers the OS with.
220    pub fn drop_target(&self) -> Option<Key> {
221        self.interaction.drop_target()
222    }
223
224    pub fn is_pressed(&self, key: Key) -> bool {
225        self.interaction.is_pressed(key)
226    }
227
228    /// Whether any member of hover group `group` (see
229    /// `NodeSpec::hover_group`) is hovered.
230    pub fn is_group_hovered(&self, group: u64) -> bool {
231        self.interaction.is_group_hovered(group)
232    }
233
234    /// Whether hover group `group` is pressed (press started on a member,
235    /// pointer still over one).
236    pub fn is_group_pressed(&self, group: u64) -> bool {
237        self.interaction.is_group_pressed(group)
238    }
239
240    /// Events raised outside `handle_input`: the `resize` a changed
241    /// viewport produced at `begin_frame`, and `on_hover` enter/leave
242    /// caused by a finished frame changing what sits under a still cursor.
243    /// Frame drivers route these after `finish_frame`; they also ride along
244    /// with the next `handle_input` result, so a driver that never calls
245    /// this merely sees them a little later.
246    pub fn take_pending_events(&mut self) -> Vec<UiEvent> {
247        let mut out = std::mem::take(&mut self.pending);
248        out.append(&mut self.interaction.take_pending());
249        self.devtools_consume(&mut out);
250        self.devtools_translate(&mut out);
251        self.stamp(&mut out);
252        self.devtools_log(&out);
253        out
254    }
255
256    /// Swaps in the hover / pressed / focus background the spec declares
257    /// for the node's (or its group's) current state: pressed wins over
258    /// keyboard-visible focus wins over hover. A disabled node keeps its
259    /// plain `bg`. Runs before easing so a `transition` tweens between
260    /// the states.
261    #[inline]
262    fn resolve_hover_style(&self, key: Key, spec: &mut NodeSpec) {
263        // Runs for every node of every frame, and almost every node declares
264        // none of this — so the early-out is one null check on the boxed
265        // group rather than three `Option`s read out of the spec, and it is
266        // inlined so the check is a branch rather than a call (C15).
267        if spec.interact.is_some() {
268            self.resolve_declared_hover_style(key, spec);
269        }
270    }
271
272    #[inline(never)]
273    fn resolve_declared_hover_style(&self, key: Key, spec: &mut NodeSpec) {
274        let Some(interact) = spec.interact.as_deref() else {
275            return;
276        };
277        let (hover_bg, pressed_bg, focus_bg, drop_bg, group) = (
278            interact.hover_bg,
279            interact.pressed_bg,
280            interact.focus_bg,
281            interact.drop_bg,
282            interact.hover_group,
283        );
284        if spec.disabled
285            || (hover_bg.is_none()
286                && pressed_bg.is_none()
287                && focus_bg.is_none()
288                && drop_bg.is_none())
289        {
290            return;
291        }
292        // Dragged files over the zone win over every pointer state: a
293        // press cannot be held while the OS holds a drag (ADR 0031,
294        // decision 3).
295        if let Some(c) = drop_bg
296            && self.interaction.is_drop_target(key)
297        {
298            spec.style.bg = c;
299            return;
300        }
301        let pressed = self.interaction.is_pressed(key)
302            || group.is_some_and(|g| self.interaction.is_group_pressed(g));
303        let hovered = pressed
304            || self.interaction.is_hovered(key)
305            || group.is_some_and(|g| self.interaction.is_group_hovered(g));
306        let focused = self.focus_visible && self.focus == Some(key);
307        if pressed && let Some(c) = pressed_bg {
308            spec.style.bg = c;
309        } else if focused && let Some(c) = focus_bg {
310            spec.style.bg = c;
311        } else if hovered && let Some(c) = hover_bg {
312            spec.style.bg = c;
313        }
314    }
315
316    /// Physical modifier state as of the last `InputEvent::Modifiers`.
317    pub fn modifiers(&self) -> crate::input::KeyMods {
318        self.interaction.modifiers()
319    }
320
321    /// Where the pointer is, in this window's logical viewport
322    /// coordinates, as of the last `CursorMoved` — `None` once it has
323    /// left the window. What a view that follows the pointer reads (the
324    /// devtools' picker outlines the node under it); a control that wants
325    /// to *react* to the pointer declares `hoverable` or `on_hover` and
326    /// lets the core do the hit test.
327    pub fn cursor(&self) -> Option<Vec2> {
328        self.interaction.cursor().map(|p| p.minus(self.dt_shift()))
329    }
330
331    // The open chain is inlined end to end (`Ui::open` → here →
332    // `open_with_key` → `Tree::push`): a `NodeSpec` is 224 bytes and moved
333    // by value at every step, and each step that is a real call is a copy
334    // of all of them. Inlined, the spec the view built travels by pointer
335    // and is copied once, into the tree (C15).
336    #[inline]
337    pub fn open(&mut self, spec: NodeSpec) -> Key {
338        let key = self.auto_key();
339        self.open_with_key(key, spec);
340        key
341    }
342
343    #[inline]
344    pub fn open_keyed(&mut self, label: &str, spec: NodeSpec) -> Key {
345        let key = self.child_key(label);
346        if self.tree.is_empty() {
347            // No frame to open into (the same no-op as `open_with_key`),
348            // and so no node for the label to name.
349            return key;
350        }
351        self.open_with_key(key, spec);
352        self.key_labels.push(key, label, self.origin);
353        key
354    }
355
356    /// The inverse of [`Self::key_of`]: the label `key` was opened under
357    /// — in the frame being built so far, else in the last one — or
358    /// `None` for an auto-keyed node or a key no frame has declared. What
359    /// a reader holding a key from an event or from `focus()` turns back
360    /// into the name the view gave it.
361    pub fn label_of(&self, key: Key) -> Option<&str> {
362        self.key_labels
363            .label_of(key)
364            .or_else(|| self.key_labels_last.label_of(key))
365    }
366
367    /// The key of the node opened under `label` (`open_keyed`; a `key`
368    /// prop in JSX or a Lua table) in the last finished frame — or, while
369    /// a frame is being built, in it so far and then in the last one. The
370    /// door for a caller that holds only strings: keys are hashes of the
371    /// path from the root, and that path runs through auto-keyed
372    /// ancestors nothing outside the build can spell, so "focus the node
373    /// I just declared" is this and not `child_key`. None when no node
374    /// declared the label. Labels are unique among siblings, not across a
375    /// tree, so two nodes may share one under different parents. A guest
376    /// asking from inside its fill is answered from the nodes
377    /// it opened and no one else's — it cannot know what the host or
378    /// another guest called theirs, and its env is a reading of its own
379    /// view; the host, whose frame it is, from its own first and from
380    /// everyone's when it opened none. Within that, the first in tree
381    /// order wins and an `ambiguous-key` warning says so.
382    pub fn key_of(&mut self, label: &str) -> Option<Key> {
383        self.find_label(label, true)
384    }
385
386    /// The one label lookup: the first node in tree order
387    /// opened under `label` in the frame being built, and — with
388    /// `fall_back` and a build under way — in the last frame when this
389    /// one has not declared it yet; an `ambiguous-key` warning when more
390    /// than one did. `key_of` falls back; `resolve_regions` runs at the
391    /// frame's end, when this frame's labels are the whole story.
392    pub(crate) fn find_label(&mut self, label: &str, fall_back: bool) -> Option<Key> {
393        let (first, count) = {
394            let mut hits = self.key_labels.find_for(label, self.origin);
395            if hits.0.is_none() && fall_back && self.building {
396                hits = self.key_labels_last.find_for(label, self.origin);
397            }
398            (hits.0?, hits.1)
399        };
400        if count > 1 {
401            self.diag
402                .raise(crate::diag::ambiguous_key(label, first, count));
403        }
404        Some(first)
405    }
406
407    /// `open_keyed` in the sibling-index namespace: the key auto-keying
408    /// would have given the `i`th child. A list that builds only rows
409    /// 900..930 opens each with its *data* index, so row 900 keeps the key
410    /// it has when the whole list is built — hover, focus, edit buffers and
411    /// tweens follow the row instead of the slot it happens to occupy.
412    #[inline]
413    pub fn open_indexed(&mut self, i: u64, spec: NodeSpec) -> Key {
414        let key = self.child_key_indexed(i);
415        let at = self.tree.len() as u32;
416        self.open_with_key(key, spec);
417        // Remembered for the node that was actually pushed, so a selection
418        // inside a virtual row can be ordered by the row's place in the
419        // *data* when the row itself is not built (ADR 0017, tier 3).
420        if self.tree.len() as u32 > at {
421            self.tree.indexed.push((at, i));
422        }
423        key
424    }
425
426    /// Declares how many indexed rows the *open* node's virtual list has,
427    /// built or not (`rowCount`): what Select All inside a `selectable`
428    /// virtual list spans, since the built rows are all the core can see.
429    /// `widgets::uniform_list` and `widgets::list`
430    /// call it on their container; a list composed by hand calls it
431    /// inside the container's `with`. Nothing, outside any node.
432    pub fn row_count(&mut self, n: u64) {
433        if self.tree.is_empty() || self.stack.is_empty() {
434            return;
435        }
436        let at = self.current();
437        self.tree.row_counts.push((at, n));
438    }
439
440    /// Opens a node under a key the caller built; see `Ui::open_key`.
441    #[inline]
442    pub fn open_key(&mut self, key: Key, spec: NodeSpec) -> Key {
443        self.open_with_key(key, spec);
444        key
445    }
446
447    #[inline]
448    pub(crate) fn open_with_key(&mut self, key: Key, spec: NodeSpec) {
449        self.open_content(key, spec, NodeContent::Container);
450    }
451
452    /// `open_with_key` with the node named `label` for `key_of`, the way
453    /// `open_keyed` names its node — for a key the caller fixed rather
454    /// than derived (a devtools tab's body).
455    pub(crate) fn open_with_key_named(&mut self, key: Key, label: &str, spec: NodeSpec) {
456        if self.tree.is_empty() {
457            return;
458        }
459        self.open_with_key(key, spec);
460        self.key_labels.push(key, label, self.origin);
461    }
462
463    /// `open_with_key` for a node that is a box in every way but what it
464    /// paints: the caller supplies the content and closes the node. What
465    /// the node asks of the frame is noted by `Tree::push`, the same for a
466    /// box, a `fragment` and every leaf.
467    fn open_content(&mut self, key: Key, mut spec: NodeSpec, content: NodeContent) {
468        if self.tree.is_empty() {
469            return;
470        }
471        self.prepare_spec(key, &mut spec);
472        // `NodeSpec::tooltip`: the hint floats on this node's `close`, the
473        // way a parsed `tooltip` prop's does. One pointer check for a node
474        // that declares no access group.
475        let tip = match spec.access.as_deref() {
476            Some(a) if a.tooltip => a.description.clone(),
477            _ => None,
478        };
479        let parent = self.current();
480        let idx = self.tree.push(parent, key, self.origin, spec, content);
481        self.stack.push(idx);
482        self.counters.push(0);
483        if let Some(tip) = tip {
484            self.spec_hint(key, &tip);
485        }
486    }
487
488    /// The hint of a node that declared [`NodeSpec::tooltip`], recorded
489    /// only while it is hovered — `close` would drop it otherwise.
490    #[cold]
491    #[inline(never)]
492    fn spec_hint(&mut self, key: Key, tip: &str) {
493        if self.is_hovered(key) {
494            self.hint(key, tip);
495        }
496    }
497
498    /// What every node's spec goes through between the door and the tree,
499    /// in this order — one pipeline for a box, a leaf, a stroke and a
500    /// fill alike (AR16: five doors ran five subsets of it, and a wedge's
501    /// `hover_bg` never painted). The one paint the environment decides
502    /// (`accent`): the theme's accent, which is the OS's where the host
503    /// reported one, the app's where it pinned one, and kui's otherwise;
504    /// before the hover resolution, so a node that
505    /// declares both still hovers to what it declared. Then the hover /
506    /// pressed / focus background for the node's state, then the eased
507    /// values a transition, entrance or keyframes put over the declared
508    /// ones.
509    #[inline]
510    fn prepare_spec(&mut self, key: Key, spec: &mut NodeSpec) {
511        if spec.accent && self.has_accent() {
512            spec.style.bg = self.theme.accent;
513        }
514        self.resolve_hover_style(key, spec);
515        self.ease_spec(key, spec);
516    }
517
518    /// The layout of a node placed by its own geometry — a stroke, a fill:
519    /// never in layout, a float at `rect` in the
520    /// parent's box space sized exactly to it, the declared float's
521    /// *anchor* kept and every sizing, clamp and scroll row overridden,
522    /// since the box is the shape's own and not a size the view chose or
523    /// a tween may lag.
524    fn float_box_for(spec: &mut NodeSpec, rect: Rect) {
525        let anchor = spec
526            .layout
527            .float
528            .map_or(crate::spec::FloatAnchor::Parent, |f| f.anchor);
529        spec.layout.float = Some(crate::spec::FloatConfig {
530            anchor,
531            offset: crate::spec::Vec2Offset {
532                x: rect.x,
533                y: rect.y,
534            },
535            // A stroke drawn in its parent's box is the parent's content,
536            // so the parent's clip holds it as it holds a child (F78; ADR
537            // 0010 decision 5, as amended by F90): the bit a declared float
538            // opts into, set here for every stroke. A viewport-anchored
539            // one escapes regardless (`FloatConfig::clipped_by_parent`).
540            clip: true,
541            ..crate::spec::FloatConfig::default()
542        });
543        spec.layout.width = Sizing::Fixed(rect.w);
544        spec.layout.height = Sizing::Fixed(rect.h);
545        spec.layout.min_w = crate::spec::Min::AUTO;
546        spec.layout.max_w = f32::INFINITY;
547        spec.layout.min_h = crate::spec::Min::AUTO;
548        spec.layout.max_h = f32::INFINITY;
549        spec.layout.clip = false;
550        spec.layout.scroll_x = false;
551        spec.layout.scroll_y = false;
552    }
553
554    #[inline]
555    pub fn close(&mut self) {
556        // The tooltip prop's third effect, for the node being closed: its
557        // hint floats below it as its last child while it is hovered. One
558        // length check per close for a frame that declared no hints.
559        if let Some((depth, _, _)) = self.hints.last()
560            && *depth == self.stack.len()
561        {
562            let (_, key, hint) = self.hints.pop().unwrap();
563            if self.is_hovered(key) {
564                crate::widgets::hover_hint(&mut Ui::wrap(self), &hint);
565            }
566        }
567        if self.stack.len() > 1 {
568            self.stack.pop();
569            self.counters.pop();
570        }
571    }
572
573    /// Records the hover hint of the node just opened (the top of the
574    /// stack): `close` floats `widgets::hover_hint` below it while it is
575    /// hovered. The one place that decides *when* a tooltip shows, so a
576    /// binding that parsed the string cannot show it some other way.
577    pub fn hint(&mut self, key: Key, text: impl Into<String>) {
578        self.hints.push((self.stack.len(), key, text.into()));
579    }
580
581    /// Opens a node the way a parsed prop list says — under the data
582    /// index, the label or the next auto key; taking keyboard focus when
583    /// `keyFocus` asked; floating its `tooltip` on `close` while hovered —
584    /// with whatever the node holds. The one door for every binding that
585    /// lowers props, so the identity match, the focus edge and the hint
586    /// are not re-derived per binding per element (they were, eight, five
587    /// and four times). A box or a fragment is left open for its children;
588    /// a `cells` grid, a `line` and a `polygon` are leaves and take no
589    /// hint, since a stroke and a fill take no input and a grid draws its
590    /// own. Returns the key.
591    pub fn open_from(&mut self, props: PropsOut, content: Content<'_>) -> Key {
592        let PropsOut {
593            spec,
594            key: label,
595            index,
596            row_count,
597            key_focus,
598            tooltip,
599            ..
600        } = props;
601        let identity = match (index, &label) {
602            (Some(i), _) => Identity::Index(i),
603            (None, Some(label)) => Identity::Label(label),
604            (None, None) => Identity::Auto,
605        };
606        let key = match identity {
607            Identity::Auto => self.auto_key(),
608            Identity::Label(l) => self.child_key(l),
609            Identity::Index(i) => self.child_key_indexed(i),
610        };
611        let at = self.tree.len() as u32;
612        let leaf = match content {
613            Content::Box => {
614                self.open_with_key(key, spec);
615                false
616            }
617            Content::Fragment(frag, params) => {
618                self.fragment_with_key(key, frag, params, spec);
619                false
620            }
621            Content::Cells(grid) => {
622                self.cells_at(key, grid, spec);
623                true
624            }
625            Content::Line(points, stroke) => {
626                self.line_with_key(key, points, stroke, spec);
627                true
628            }
629            Content::Polygon(points) => {
630                self.polygon_with_key(key, points, spec);
631                true
632            }
633            Content::Path(ops, rule, stroke, turn) => {
634                self.path_with_key(key, ops, rule, stroke, turn, spec);
635                true
636            }
637            Content::PathD(d, rule, stroke, turn) => {
638                self.path_node_d(key, d, rule, stroke, turn, spec);
639                true
640            }
641        };
642        // Bookkeeping for the node that was actually pushed: the label
643        // `key_of` resolves through, or the data index a selection inside
644        // a virtual row is ordered by when the row is not built (ADR 0017).
645        if self.tree.len() as u32 > at {
646            match identity {
647                Identity::Label(l) => self.key_labels.push(key, l, self.origin),
648                Identity::Index(i) => self.tree.indexed.push((at, i)),
649                Identity::Auto => {}
650            }
651            if let Some(n) = row_count {
652                self.tree.row_counts.push((at, n));
653            }
654        }
655        if key_focus {
656            self.set_key_focus(Some(key));
657        }
658        if let Some(hint) = tooltip
659            && !leaf
660        {
661            self.hint(key, hint);
662        }
663        key
664    }
665
666    /// The root the way a parsed prop list says: its title, whether it
667    /// wants the window on top, its keyboard secure or its Option keys as
668    /// Alt, the windows it declares, its spec, and
669    /// keyboard focus on it when asked — what a binding's root op does,
670    /// once.
671    pub fn configure_root_from(&mut self, props: PropsOut) {
672        if let Some(title) = &props.title {
673            self.set_window_title(title);
674        }
675        if props.always_on_top {
676            self.set_always_on_top(true);
677        }
678        if props.secure_input {
679            self.set_secure_input(true);
680        }
681        if props.option_as_alt != crate::OptionAsAlt::None {
682            self.set_option_as_alt(props.option_as_alt);
683        }
684        for (name, cfg) in &props.windows {
685            self.declare_window(name, *cfg);
686        }
687        self.configure_root(props.spec);
688        if props.key_focus {
689            let root = self.root_key();
690            self.set_key_focus(Some(root));
691        }
692    }
693
694    pub fn text_node(&mut self, content: &str, style: TextStyle) {
695        if self.tree.is_empty() {
696            return;
697        }
698        // A style that named no colour takes the theme's foreground here,
699        // at the one door text comes through, so the shaping cache, the
700        // display list and every binding downstream see a real colour
701        // (ADR 0019).
702        let style = style.or_fg(self.theme.fg);
703        let tid = {
704            let sess = &mut *self.session.state();
705            self.text
706                .add(content, &style, &sess.resources, &mut sess.fonts)
707        };
708        let key = self.auto_key();
709        let parent = self.current();
710        self.tree.push(
711            parent,
712            key,
713            self.origin,
714            NodeSpec::default(),
715            NodeContent::Text(tid),
716        );
717    }
718
719    /// A cell grid as one leaf node, sized `cols × cell_w` by `rows ×
720    /// cell_h`. `spec` is the node's:
721    /// an `on_key` makes it the terminal's sink, an `on_click` / `on_drag`
722    /// carry `cell: {row, col}` on their events.
723    pub fn cells(&mut self, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
724        let key = self.auto_key();
725        self.cells_at(key, grid, spec);
726    }
727
728    /// [`Self::cells`] under a declared key.
729    pub fn cells_keyed(&mut self, label: &str, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
730        if self.tree.is_empty() {
731            return;
732        }
733        let key = self.child_key(label);
734        self.cells_at(key, grid, spec);
735        // Like every other keyed door: the label after the node, so a
736        // frame with no root records no name (AR16).
737        self.key_labels.push(key, label, self.origin);
738    }
739
740    /// [`Self::cells`] under a data index; see [`Self::open_indexed`].
741    pub fn cells_indexed(&mut self, i: u64, grid: &crate::cells::CellGrid<'_>, spec: NodeSpec) {
742        let key = self.child_key_indexed(i);
743        self.cells_at(key, grid, spec);
744    }
745
746    fn cells_at(&mut self, key: Key, grid: &crate::cells::CellGrid<'_>, mut spec: NodeSpec) {
747        if self.tree.is_empty() {
748            return;
749        }
750        // The node's box is a box like any leaf's: its `hoverBg` lights
751        // and its `transition` tweens the bg, the opacity, the size. The
752        // cells inside it are a picture the app redraws, and nothing here
753        // touches them (AR5).
754        self.prepare_spec(key, &mut spec);
755        let cid = self.cells.add(key, grid);
756        let parent = self.current();
757        self.tree
758            .push(parent, key, self.origin, spec, NodeContent::Cells(cid));
759    }
760
761    /// An editable text node. State (buffer, cursor, selection) is retained
762    /// by key across frames; edits arrive via `handle_input` and come back to
763    /// the host as "changed"/"submit" events. Read with `edit_text`.
764    pub fn text_edit(
765        &mut self,
766        label: &str,
767        initial: &str,
768        opts: &EditOptions,
769        mut spec: NodeSpec,
770    ) -> Key {
771        if self.tree.is_empty() {
772            return Key::ROOT;
773        }
774        let key = self.child_key(label);
775        self.prepare_spec(key, &mut spec);
776        // The same stamp the two text funnels make: an editor that named
777        // no text colour and no selection tint takes the theme's, so a
778        // field and a label beside it agree on both (ADR 0019).
779        let opts = &EditOptions {
780            style: opts.style.or_fg(self.theme.fg),
781            accent: Some(opts.accent.unwrap_or(self.theme.selection)),
782            ..opts.clone()
783        };
784        let edge = {
785            let origin = self.origin;
786            let scale = self.scale;
787            let sess = &mut *self.session.state();
788            // A `set_edit_text` by label, held for the frame that would
789            // declare the name (backlog F32): claimed here, where the
790            // label and its key are both in hand, and before `declare`,
791            // which is what turns it into this key's seed.
792            self.edit
793                .claim_label(key, label, &mut sess.fonts, &sess.resources);
794            self.edit.declare(
795                key,
796                initial,
797                opts,
798                origin,
799                scale,
800                &mut sess.fonts,
801                &sess.resources,
802            )
803        };
804        // Autofocus takes the keyboard only while nothing holds it — never
805        // from a control Tab landed on — and only on the frame the editor
806        // starts being declared (`docs/adr/0022`, decision 9): asked every
807        // frame, it would take focus straight back from every blur, and an
808        // app with an autofocus field could never have nothing focused.
809        if opts.autofocus && edge && self.focus.is_none() && !spec.disabled {
810            self.move_focus(Some(key));
811        }
812        let parent = self.current();
813        self.tree
814            .push(parent, key, self.origin, spec, NodeContent::Edit(key));
815        // A leaf keyed by its label, like `open_keyed`: `key_of` must find
816        // the editor an app wants to focus by name.
817        self.key_labels.push(key, label, self.origin);
818        key
819    }
820
821    /// A registered image (see `Resources::add_image`). Fit sizing takes
822    /// the image's pixel dimensions as logical px; a Fit height against a
823    /// resolved width preserves the aspect ratio. `style.radius` rounds the
824    /// corners. Linear sampling, stretched to the box: [`Self::image_node_with`]
825    /// takes the two rows that say otherwise.
826    pub fn image_node(&mut self, id: crate::resources::ImageId, spec: NodeSpec) {
827        self.image_node_with(id, crate::resources::ImageOpts::default(), spec);
828    }
829
830    /// [`Self::image_node`] with its `sampling` and `fit` rows: how texels are
831    /// read between pixels, and how the pixels
832    /// meet a box of another aspect. The box — its layout, hit region and
833    /// access rect — is the same in every mode.
834    pub fn image_node_with(
835        &mut self,
836        id: crate::resources::ImageId,
837        opts: crate::resources::ImageOpts,
838        mut spec: NodeSpec,
839    ) {
840        if self.tree.is_empty() {
841            return;
842        }
843        let key = self.auto_key();
844        self.prepare_spec(key, &mut spec);
845        let parent = self.current();
846        self.tree
847            .push(parent, key, self.origin, spec, NodeContent::Image(id, opts));
848    }
849
850    /// A box a registered WGSL function paints.
851    ///
852    /// An ordinary node in every other respect: it lays out where it is
853    /// declared, sizes from `spec`, rounds by `radius`, clips, fades with
854    /// its subtree's opacity, takes input like any box, and may hold
855    /// children — which paint over it, so a gradient card is a `fragment`
856    /// with a title and buttons inside it.
857    ///
858    /// It has **no intrinsic size**: unlike an image there is nothing to
859    /// measure, so a fragment with no `width` / `height` / `fill` is zero
860    /// by zero and draws nothing. Size it.
861    ///
862    /// `params` is up to sixteen numbers, positional, zero-padded, read by
863    /// the shader as four `vec4<f32>`; more than sixteen are dropped with
864    /// a `fragment-params-truncated` warning. A handle that is not live in
865    /// this session draws nothing, as every resource kind does — and so
866    /// does one whose `image` (`FragmentId::with_image`) is not, which is
867    /// the removal order: the image goes, the fragment reading it draws
868    /// the fallback, and the handle it kept is a `foreign-resource` miss
869    /// like any other.
870    pub fn fragment_node(
871        &mut self,
872        frag: impl Into<crate::fragment::FragmentRef>,
873        params: &[f32],
874        spec: NodeSpec,
875    ) -> Key {
876        let key = self.open_fragment(frag, params, spec);
877        self.close();
878        key
879    }
880
881    /// Opens a fragment as a parent: its children paint over it, which is
882    /// what a gradient card with a title and buttons in it is. Balance it
883    /// with [`Self::close`], or use `Ui::fragment_with`.
884    pub fn open_fragment(
885        &mut self,
886        frag: impl Into<crate::fragment::FragmentRef>,
887        params: &[f32],
888        spec: NodeSpec,
889    ) -> Key {
890        if self.tree.is_empty() {
891            return Key::ROOT;
892        }
893        let key = self.auto_key();
894        self.fragment_with_key(key, frag.into(), params, spec);
895        key
896    }
897
898    /// [`Self::fragment_node`] under a label key, for a fragment that
899    /// transitions or exits and needs a stable identity across frames.
900    pub fn fragment_node_keyed(
901        &mut self,
902        label: &str,
903        frag: impl Into<crate::fragment::FragmentRef>,
904        params: &[f32],
905        spec: NodeSpec,
906    ) -> Key {
907        let key = self.open_fragment_keyed(label, frag, params, spec);
908        self.close();
909        key
910    }
911
912    /// [`Self::open_fragment`] under a label key.
913    pub fn open_fragment_keyed(
914        &mut self,
915        label: &str,
916        frag: impl Into<crate::fragment::FragmentRef>,
917        params: &[f32],
918        spec: NodeSpec,
919    ) -> Key {
920        if self.tree.is_empty() {
921            return Key::ROOT;
922        }
923        let key = self.child_key(label);
924        self.fragment_with_key(key, frag.into(), params, spec);
925        self.key_labels.push(key, label, self.origin);
926        key
927    }
928
929    /// [`Self::open_fragment`] under a data index; see [`Self::open_indexed`].
930    pub fn open_fragment_indexed(
931        &mut self,
932        i: u64,
933        frag: impl Into<crate::fragment::FragmentRef>,
934        params: &[f32],
935        spec: NodeSpec,
936    ) -> Key {
937        if self.tree.is_empty() {
938            return Key::ROOT;
939        }
940        let key = self.child_key_indexed(i);
941        self.fragment_with_key(key, frag.into(), params, spec);
942        key
943    }
944
945    fn fragment_with_key(
946        &mut self,
947        key: Key,
948        frag: crate::fragment::FragmentRef,
949        params: &[f32],
950        spec: NodeSpec,
951    ) {
952        let (params, dropped) = crate::fragment::params_of(params);
953        if dropped > 0 {
954            self.diag.raise(Warning {
955                code: crate::diag::FRAGMENT_PARAMS_TRUNCATED,
956                key,
957                message: format!(
958                    "a fragment takes sixteen params and {} were declared, so the last {dropped}                      were dropped; pack what the shader needs into the sixteen it has",
959                    params.len() + dropped
960                ),
961            });
962        }
963        let draw = self.fragments.push(crate::fragment::Draw {
964            id: frag.id,
965            image: frag.image,
966            params,
967        });
968        self.open_content(key, spec, NodeContent::Fragment(draw));
969    }
970
971    /// A stroke through `points` in the parent's box space: one round-capped
972    /// segment for two points, a polyline for more, a smooth curve through
973    /// them with [`Stroke::curve`].
974    ///
975    /// Never in layout. The node is a float sized to the stroke's padded
976    /// bounding box, so it takes no room in a row or column, and `spec`'s
977    /// sizing, clamps, padding, gap and alignment are ignored. What `spec`
978    /// carries that matters: `transition` (the colour eases — it rides in
979    /// the `bg` slot — and `slide`, `enter` and `exit` offsets move the
980    /// float), `opacity`, `on_layout` (reports the bounding box), a
981    /// declared `float` whose *anchor* is kept (`FloatAnchor::Viewport`
982    /// reads the points in viewport space), and `role` / `label`, which are
983    /// honoured like any node's; without them a line has no access row —
984    /// unless it takes input, when it derives one as a box would. Input
985    /// is hit by *shape*: a press within half the stroke's
986    /// width of any piece (at least `MIN_STROKE_GRAB` wide) hits it, and
987    /// a press elsewhere in its box falls through to what is under it.
988    /// Fewer than two points draw nothing.
989    ///
990    /// Consecutive segments overlap at their round caps, which is the
991    /// join: exact for an opaque stroke, and a translucent one
992    /// double-blends there, the way a faded subtree shows its seams.
993    pub fn line_node(&mut self, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
994        if self.tree.is_empty() {
995            return;
996        }
997        let key = self.auto_key();
998        self.line_with_key(key, points, stroke, spec);
999    }
1000
1001    /// [`Self::line_node`] under a label key, for a stroke that transitions
1002    /// or exits and needs a stable identity across frames.
1003    pub fn line_node_keyed(
1004        &mut self,
1005        label: &str,
1006        points: &[Vec2],
1007        stroke: Stroke,
1008        spec: NodeSpec,
1009    ) {
1010        if self.tree.is_empty() {
1011            return;
1012        }
1013        let key = self.child_key(label);
1014        self.line_with_key(key, points, stroke, spec);
1015        // Like every other keyed door: the label `key_of` resolves through.
1016        self.key_labels.push(key, label, self.origin);
1017    }
1018
1019    /// [`Self::line_node`] under a data index; see [`Self::open_indexed`].
1020    pub fn line_node_indexed(&mut self, i: u64, points: &[Vec2], stroke: Stroke, spec: NodeSpec) {
1021        if self.tree.is_empty() {
1022            return;
1023        }
1024        let key = self.child_key_indexed(i);
1025        self.line_with_key(key, points, stroke, spec);
1026    }
1027
1028    fn line_with_key(&mut self, key: Key, points: &[Vec2], stroke: Stroke, mut spec: NodeSpec) {
1029        let Some((id, rect)) = self.lines.push(points, stroke) else {
1030            return;
1031        };
1032        // The stroke colour rides in the slot backgrounds tween through, so
1033        // `transition`, `enter` and `exit` reach it with no slot of its own;
1034        // nothing else of the box vocabulary applies to a stroke.
1035        spec.style.bg = stroke.color;
1036        spec.style.border_w = 0.0;
1037        spec.style.border_color = Color::TRANSPARENT;
1038        spec.style.shadow = crate::spec::Shadow::default();
1039        // The same pipeline as a box's, so a stroke's `hover_bg` is the
1040        // colour it takes under the pointer and `accent` is honoured.
1041        self.prepare_spec(key, &mut spec);
1042        // The box is the stroke's own, and the points are stored relative
1043        // to it.
1044        Self::float_box_for(&mut spec, rect);
1045        let parent = self.current();
1046        self.tree
1047            .push(parent, key, self.origin, spec, NodeContent::Line(id));
1048    }
1049
1050    /// A filled polygon through `points` in the parent's box space:
1051    /// up to
1052    /// eight vertices, the fill in `spec`'s `bg`, painted by the stock
1053    /// polygon fragment the core registers itself.
1054    ///
1055    /// Placed exactly as a line is: never in
1056    /// layout, a float sized to the points' bounding box inflated by a
1057    /// logical pixel for the edge ramp, so it takes no room in a row or
1058    /// column and `spec`'s sizing, clamps, padding, gap and alignment are
1059    /// ignored. `transition` eases the fill through the `bg` slot, and
1060    /// `slide`, `enter` and `exit` move the float; a declared `float`
1061    /// keeps its *anchor*; `role` and `label` are honoured, and without
1062    /// them a polygon has no access row unless it takes input, when it
1063    /// derives one as a box would (a clickable wedge is a button). Input
1064    /// is hit by *shape*: a press inside the outline hits it,
1065    /// one in its box but outside the outline falls through to what is
1066    /// under. Fewer than three points draw nothing; a ninth and later are
1067    /// dropped with `polygon-points-truncated`. The outline may be
1068    /// concave; a self-intersecting one fills even-odd, its overlaps
1069    /// unfilled.
1070    pub fn polygon_node(&mut self, points: &[Vec2], spec: NodeSpec) {
1071        if self.tree.is_empty() {
1072            return;
1073        }
1074        let key = self.auto_key();
1075        self.polygon_with_key(key, points, spec);
1076    }
1077
1078    /// [`Self::polygon_node`] under a label key.
1079    pub fn polygon_node_keyed(&mut self, label: &str, points: &[Vec2], spec: NodeSpec) {
1080        if self.tree.is_empty() {
1081            return;
1082        }
1083        let key = self.child_key(label);
1084        self.polygon_with_key(key, points, spec);
1085        self.key_labels.push(key, label, self.origin);
1086    }
1087
1088    /// [`Self::polygon_node`] under a data index; see [`Self::open_indexed`].
1089    pub fn polygon_node_indexed(&mut self, i: u64, points: &[Vec2], spec: NodeSpec) {
1090        if self.tree.is_empty() {
1091            return;
1092        }
1093        let key = self.child_key_indexed(i);
1094        self.polygon_with_key(key, points, spec);
1095    }
1096
1097    /// The stock polygon fragment's handle, registered on first use and
1098    /// again after `remove_fragment` forgot it.
1099    fn stock_polygon(&mut self) -> Option<crate::resources::FragmentId> {
1100        if let Some(id) = self.stock_polygon {
1101            return Some(id);
1102        }
1103        let id = self.add_fragment(crate::fragment::POLYGON);
1104        self.stock_polygon = id;
1105        id
1106    }
1107
1108    fn polygon_with_key(&mut self, key: Key, points: &[Vec2], mut spec: NodeSpec) {
1109        if points.len() < 3 {
1110            return;
1111        }
1112        if points.len() > crate::fragment::POLYGON_MAX_POINTS {
1113            self.diag.raise(Warning {
1114                code: crate::diag::POLYGON_POINTS_TRUNCATED,
1115                key,
1116                message: format!(
1117                    "a polygon takes {} points and {} were declared, so the last {} were \
1118                     dropped; split it in two",
1119                    crate::fragment::POLYGON_MAX_POINTS,
1120                    points.len(),
1121                    points.len() - crate::fragment::POLYGON_MAX_POINTS
1122                ),
1123            });
1124        }
1125        let points = &points[..points.len().min(crate::fragment::POLYGON_MAX_POINTS)];
1126        let Some(id) = self.stock_polygon() else {
1127            return;
1128        };
1129        // The box: the points' bounds, a logical pixel out on every side
1130        // so the one-pixel edge ramp is never cut by the quad's own edge.
1131        let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
1132        for p in points {
1133            x0 = x0.min(p.x);
1134            y0 = y0.min(p.y);
1135            x1 = x1.max(p.x);
1136            y1 = y1.max(p.y);
1137        }
1138        let rect = Rect::new(x0 - 1.0, y0 - 1.0, x1 - x0 + 2.0, y1 - y0 + 2.0);
1139        // The vertices, normalised to that box; the last repeated to pad,
1140        // which the stock source reads as a zero-length edge and skips.
1141        let mut params = [0.0f32; 16];
1142        let last = points[points.len() - 1];
1143        for i in 0..crate::fragment::POLYGON_MAX_POINTS {
1144            let p = points.get(i).copied().unwrap_or(last);
1145            params[i * 2] = (p.x - rect.x) / rect.w;
1146            params[i * 2 + 1] = (p.y - rect.y) / rect.h;
1147        }
1148        let draw = self.fragments.push(crate::fragment::Draw {
1149            id,
1150            image: None,
1151            params,
1152        });
1153        // The fill rides in `bg`, which `transition`, `enter` and `exit`
1154        // already ease — and which `hover_bg` and `accent` swap, through
1155        // the same pipeline as a box's; nothing else of the box vocabulary
1156        // applies.
1157        spec.style.border_w = 0.0;
1158        spec.style.border_color = Color::TRANSPARENT;
1159        spec.style.shadow = crate::spec::Shadow::default();
1160        self.prepare_spec(key, &mut spec);
1161        Self::float_box_for(&mut spec, rect);
1162        let parent = self.current();
1163        self.tree
1164            .push(parent, key, self.origin, spec, NodeContent::Polygon(draw));
1165    }
1166
1167    /// A path — any outline, SVG's `d` — filled with `spec`'s `bg` by the
1168    /// path's rule and stroked by its stroke if it has one
1169    /// (`docs/adr/0040-a-path-is-a-mask-in-the-atlas.md`).
1170    ///
1171    /// Placed exactly as a line is: never in layout, a float sized to the
1172    /// outline's bounding box two logical pixels out (and half the stroke's
1173    /// width further), so it takes no room in a row or column and
1174    /// `spec`'s sizing, clamps, padding, gap and alignment are ignored.
1175    /// `transition` eases the fill through the `bg` slot, and `slide`,
1176    /// `enter` and `exit` move the float; a declared `float` keeps its
1177    /// *anchor*; `role` and `label` are honoured, and without them a path
1178    /// has no access row unless it takes input, when it derives one as a
1179    /// box would. Input is hit by *shape*: a press inside the outline by
1180    /// the fill rule hits it, one in its box past the outline falls
1181    /// through to what is under. A path with no outline draws nothing.
1182    ///
1183    /// The outline is rasterized once per shape, scale and quarter-pixel
1184    /// position into the glyph atlas and drawn as a glyph-mask quad; the
1185    /// fill bleeds half a pixel so two paths sharing an edge meet without
1186    /// the background showing through. A path whose ops change twice within
1187    /// a few frames, or whose mask is a quarter of the biggest atlas page or
1188    /// more, draws from a texture of its own instead.
1189    pub fn path_node(&mut self, path: &crate::path::Path, spec: NodeSpec) {
1190        if self.tree.is_empty() {
1191            return;
1192        }
1193        let key = self.auto_key();
1194        self.path_with_key(
1195            key,
1196            path.ops(),
1197            path.rule(),
1198            path.stroke(),
1199            path.turn(),
1200            spec,
1201        );
1202    }
1203
1204    /// [`Self::path_node`] under a label key.
1205    pub fn path_node_keyed(&mut self, label: &str, path: &crate::path::Path, spec: NodeSpec) {
1206        if self.tree.is_empty() {
1207            return;
1208        }
1209        let key = self.child_key(label);
1210        self.path_with_key(
1211            key,
1212            path.ops(),
1213            path.rule(),
1214            path.stroke(),
1215            path.turn(),
1216            spec,
1217        );
1218        self.key_labels.push(key, label, self.origin);
1219    }
1220
1221    /// [`Self::path_node`] under a data index; see [`Self::open_indexed`].
1222    pub fn path_node_indexed(&mut self, i: u64, path: &crate::path::Path, spec: NodeSpec) {
1223        if self.tree.is_empty() {
1224            return;
1225        }
1226        let key = self.child_key_indexed(i);
1227        self.path_with_key(
1228            key,
1229            path.ops(),
1230            path.rule(),
1231            path.stroke(),
1232            path.turn(),
1233            spec,
1234        );
1235    }
1236
1237    /// SVG path data to a [`crate::path::Path`], through the one parser
1238    /// every binding's `d` goes through (`Path::parse`, reached here as
1239    /// the door the C API's `kui_path_parse` is). `Err` names the byte.
1240    pub fn parse_path(&self, d: &str) -> Result<crate::path::Path, crate::path::PathError> {
1241        crate::path::Path::parse(d)
1242    }
1243
1244    /// [`Self::path_node`] from SVG path data, parsed by the one parser
1245    /// every binding goes through; data that does not parse raises
1246    /// `path-malformed` under the node's key and draws nothing.
1247    pub fn path_d_node(
1248        &mut self,
1249        d: &str,
1250        rule: crate::path::FillRule,
1251        stroke: Option<Stroke>,
1252        turn: Option<crate::path::Turn>,
1253        spec: NodeSpec,
1254    ) {
1255        if self.tree.is_empty() {
1256            return;
1257        }
1258        let key = self.auto_key();
1259        self.path_node_d(key, d, rule, stroke, turn, spec);
1260    }
1261
1262    /// [`Self::path_d_node`] under a label key.
1263    pub fn path_d_node_keyed(
1264        &mut self,
1265        label: &str,
1266        d: &str,
1267        rule: crate::path::FillRule,
1268        stroke: Option<Stroke>,
1269        turn: Option<crate::path::Turn>,
1270        spec: NodeSpec,
1271    ) {
1272        if self.tree.is_empty() {
1273            return;
1274        }
1275        let key = self.child_key(label);
1276        self.path_node_d(key, d, rule, stroke, turn, spec);
1277        self.key_labels.push(key, label, self.origin);
1278    }
1279
1280    /// [`Self::path_d_node`] under a key the caller derived.
1281    pub fn path_node_d(
1282        &mut self,
1283        key: Key,
1284        d: &str,
1285        rule: crate::path::FillRule,
1286        stroke: Option<Stroke>,
1287        turn: Option<crate::path::Turn>,
1288        spec: NodeSpec,
1289    ) {
1290        // The string a key declared last frame is the string it declares
1291        // this frame, nearly always: its ops are kept, and the parse is
1292        // paid when the string changes.
1293        let hash = crate::key::hash_bulk(d.as_bytes());
1294        let kept = self
1295            .path_parsed
1296            .remove(&key)
1297            .filter(|p| p.hash == hash && p.len == d.len());
1298        let ops = match kept {
1299            Some(p) => p.ops,
1300            None => match crate::path::Path::parse(d) {
1301                Ok(path) => path.into_ops(),
1302                Err(e) => {
1303                    self.diag.raise(Warning {
1304                        code: crate::diag::PATH_MALFORMED,
1305                        key,
1306                        message: format!("the path's `d` did not parse: expected {e}"),
1307                    });
1308                    return;
1309                }
1310            },
1311        };
1312        self.path_with_key(key, &ops, rule, stroke, turn, spec);
1313        self.path_parsed.insert(
1314            key,
1315            crate::path::Parsed {
1316                hash,
1317                len: d.len(),
1318                seen: self.frame_no,
1319                ops,
1320            },
1321        );
1322    }
1323
1324    fn path_with_key(
1325        &mut self,
1326        key: Key,
1327        ops: &[crate::path::PathOp],
1328        rule: crate::path::FillRule,
1329        stroke: Option<Stroke>,
1330        turn: Option<crate::path::Turn>,
1331        mut spec: NodeSpec,
1332    ) {
1333        // A number that is not one - `1e99` in `d` is an infinity, a
1334        // chart's 0/0 a NaN - has no outline to draw: the bounds would
1335        // drop it and the rasterizer would not.
1336        // The same for its turn, which the box does not depend on and so
1337        // could not catch.
1338        let turn_ok = turn.is_none_or(|t| {
1339            t.turns.is_finite() && t.pivot.is_none_or(|p| p.x.is_finite() && p.y.is_finite())
1340        });
1341        if !turn_ok || !ops.iter().all(crate::path::PathOp::is_finite) {
1342            self.diag.raise(Warning {
1343                code: crate::diag::PATH_MALFORMED,
1344                key,
1345                message: "the path holds a number that is not finite (a NaN or an \
1346                          infinity), among its coordinates or in its turn"
1347                    .into(),
1348            });
1349            return;
1350        }
1351        let stroke_w = stroke.map_or(0.0, |s| s.width.max(0.0));
1352        let Some((id, rect)) = self.paths.push(ops, rule, stroke_w, turn) else {
1353            return;
1354        };
1355        // The mask is the box at the frame's scale; past what a texture
1356        // can hold it draws nothing, and says so once per key. A box
1357        // that is not finite - a coordinate past what an f32 holds, a NaN
1358        // among the ops or in the turn - is past it too: `max` drops a
1359        // NaN, so the side alone would let one through.
1360        let side = (rect.w.max(rect.h) * self.scale).ceil();
1361        let finite = [rect.x, rect.y, rect.w, rect.h]
1362            .iter()
1363            .all(|v| v.is_finite());
1364        if !finite || side + 2.0 > crate::path::MAX_MASK_SIDE as f32 {
1365            self.diag.raise(Warning {
1366                code: crate::diag::PATH_TOO_LARGE,
1367                key,
1368                message: format!(
1369                    "the path's mask would be {side} px on a side at this scale, and a \
1370                     texture holds {} at most; draw it smaller, or as several paths",
1371                    crate::path::MAX_MASK_SIDE
1372                ),
1373            });
1374            return;
1375        }
1376        // A key whose ops changed twice within a few frames is animating:
1377        // its masks go to a texture of their own rather than churning the
1378        // atlas (ADR 0040, decision 8). One-way, as an updated image's
1379        // backing is. One change is a new shape and a new slot.
1380        let hash = self.paths.run(id).0.hash;
1381        let now = self.frame_no;
1382        let motion = match self.path_motion.get(&key) {
1383            Some(&m) if m.hash != hash => crate::path::Motion {
1384                hash,
1385                seen: now,
1386                changed: now,
1387                animating: m.animating
1388                    || (m.changed != 0 && now - m.changed <= crate::path::ANIMATING_WINDOW),
1389            },
1390            Some(&m) => crate::path::Motion { seen: now, ..m },
1391            None => crate::path::Motion {
1392                hash,
1393                seen: now,
1394                changed: 0,
1395                animating: false,
1396            },
1397        };
1398        if motion.animating {
1399            self.paths.set_animating(id);
1400        }
1401        self.path_motion.insert(key, motion);
1402        // The fill rides in `bg`, which `transition`, `enter` and `exit`
1403        // ease and `hover_bg` and `accent` swap; the stroke rides in the
1404        // border slots, which is what a border is to a box.
1405        spec.style.border_w = stroke_w;
1406        spec.style.border_color = stroke.map_or(Color::TRANSPARENT, |s| s.color);
1407        spec.style.shadow = crate::spec::Shadow::default();
1408        self.prepare_spec(key, &mut spec);
1409        Self::float_box_for(&mut spec, rect);
1410        let parent = self.current();
1411        self.tree
1412            .push(parent, key, self.origin, spec, NodeContent::Path(id));
1413    }
1414
1415    /// A paragraph of styled spans, shaped and wrapped as one flow.
1416    pub fn rich_text_node(&mut self, spans: &[Span<'_>], base: TextStyle) {
1417        if self.tree.is_empty() {
1418            return;
1419        }
1420        // The paragraph's own colour, which each span falls back to.
1421        let base = base.or_fg(self.theme.fg);
1422        let tid = {
1423            let sess = &mut *self.session.state();
1424            self.text
1425                .add_rich(spans, &base, &sess.resources, &mut sess.fonts)
1426        };
1427        let key = self.auto_key();
1428        let parent = self.current();
1429        self.tree.push(
1430            parent,
1431            key,
1432            self.origin,
1433            NodeSpec::default(),
1434            NodeContent::Text(tid),
1435        );
1436    }
1437}