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