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