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