Skip to main content

kui_core/
tree.rs

1//! [`Tree`]: the per-frame flat tree the core builds, lays out and emits.
2//!
3//! The tree is a set of parallel `Vec`s, one entry per node, rebuilt from
4//! scratch every frame with their capacities retained. Nodes are stored in
5//! DFS preorder (a parent always precedes its children, and preorder is
6//! paint order) and linked through `parent`, `first_child` and
7//! `next_sibling` indices. Layout writes `size` and `pos` into it; emission
8//! reads them. [`OriginId`] says which frontend (the host app, an
9//! extension, the core's own surfaces) opened each node.
10//!
11//! An app never touches this type: it builds through [`Ui`](crate::ui::Ui)
12//! and reads back through `Core`. It is public for a custom runner or a
13//! test that drives [`layout::compute`](crate::layout::compute) directly.
14
15use crate::geom::{Size, Vec2};
16use crate::key::Key;
17use crate::spec::NodeSpec;
18
19pub const NIL: u32 = u32::MAX;
20
21/// Which frontend produced a node: 0 is the host app, extensions get 1+.
22#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
23pub struct OriginId(pub u16);
24
25impl OriginId {
26    pub const HOST: OriginId = OriginId(0);
27    /// The core's own devtools panel: a node opened
28    /// under it is the panel's, and an event that carries it is acted on
29    /// inside `handle_input` and never handed out. Reserved at the top of
30    /// the range so no extension list ever reaches it.
31    pub const DEVTOOLS: OriginId = OriginId(u16::MAX);
32    /// The core's own context menu and the menu bar it draws: the same
33    /// isolation the
34    /// devtools have — a node opened under one of these is the surface's,
35    /// its events are taken back inside `handle_input` and never handed
36    /// out, and no key list has to remember which nodes those were.
37    pub const MENU: OriginId = OriginId(u16::MAX - 1);
38    pub const MENU_BAR: OriginId = OriginId(u16::MAX - 2);
39
40    /// Whether nodes of this origin are one of the core's own surfaces.
41    pub fn is_core_surface(self) -> bool {
42        matches!(self, Self::DEVTOOLS | Self::MENU | Self::MENU_BAR)
43    }
44}
45
46/// Index into the frame's text list (owned by `TextSystem`).
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct TextId(pub u32);
49
50#[derive(Clone, Copy, Debug, PartialEq)]
51pub enum NodeContent {
52    Container,
53    Text(TextId),
54    /// Editable text; retained state lives in the core's `EditStore`.
55    Edit(Key),
56    /// A host-registered image (see `Resources`), drawn from the atlas or
57    /// from a texture of its own as the entry's backing says, met by its
58    /// box as `opts` say.
59    Image(crate::resources::ImageId, crate::resources::ImageOpts),
60    /// A stroke through a run of points: one segment quad per straight
61    /// piece (see `crate::line`). The node is a float sized to the
62    /// stroke's bounding box, and its `bg` is the stroke colour.
63    Line(crate::line::LineId),
64    /// A cell grid (see `crate::cells`): a terminal's screen as one node.
65    Cells(crate::cells::CellsId),
66    /// A box a registered WGSL function paints (see `crate::fragment`).
67    /// The handle and the sixteen parameters live in the frame's
68    /// `FragmentList`; the node carries only where.
69    Fragment(crate::fragment::FragmentDrawId),
70    /// A filled polygon: a float sized to its own
71    /// bounding box like a line, painted by the stock polygon fragment
72    /// whose draw sits in the frame's `FragmentList` like any fragment's,
73    /// its `bg` the fill. No hit region, no access row.
74    Polygon(crate::fragment::FragmentDrawId),
75}
76
77impl Tree {
78    /// One past the last node of `i`'s subtree. Preorder storage makes a
79    /// subtree a contiguous index range ending at the next node that is a
80    /// sibling of `i` or of one of its ancestors.
81    pub fn subtree_end(&self, i: usize) -> usize {
82        let mut n = i as u32;
83        loop {
84            if self.next_sibling[n as usize] != NIL {
85                return self.next_sibling[n as usize] as usize;
86            }
87            n = self.parent[n as usize];
88            if n == NIL {
89                return self.len();
90            }
91        }
92    }
93}
94
95/// One frame's nodes as parallel arrays in preorder; see the
96/// [module docs](self).
97#[derive(Default)]
98pub struct Tree {
99    pub keys: Vec<Key>,
100    pub origins: Vec<OriginId>,
101    pub specs: Vec<NodeSpec>,
102    pub content: Vec<NodeContent>,
103
104    pub parent: Vec<u32>,
105    pub first_child: Vec<u32>,
106    pub last_child: Vec<u32>,
107    pub next_sibling: Vec<u32>,
108
109    // Filled by the layout pass.
110    pub size: Vec<Size>,
111    pub pos: Vec<Vec2>,
112    /// Max scroll offset per axis (zero for non-scroll nodes).
113    pub scroll_max: Vec<Vec2>,
114    /// Which wrap line of its parent a node sits on, from the main-axis
115    /// pass. Zero everywhere but under a wrapping container, and the
116    /// in-flow children of one line are always a contiguous sibling run,
117    /// so a line is a range rather than a list.
118    pub line: Vec<u32>,
119    /// Each node's first baseline below its top, logical px, where text
120    /// measured one (`NaN` elsewhere). Filled by the fit-height pass, and
121    /// only on a frame with a baseline row (`any_baseline`); empty
122    /// otherwise.
123    pub baseline: Vec<f32>,
124
125    // Set by `push`, cleared by `clear`: what this frame declared at all,
126    // so a pass whose work exists for one feature can skip it wholesale
127    // when no node asked for that feature. A float check in a layout pass
128    // is a scattered read through the spec of every child of every node;
129    // behind a flag that is false on nearly every frame it is one
130    // predicted branch.
131    /// Whether any node declares `float`.
132    pub any_float: bool,
133    /// Whether any node declares a size expression as a clamp (a
134    /// negative `max_w`, a `Min::calc`): layout resolves
135    /// them only then.
136    pub any_calc_bound: bool,
137    /// Whether any float is anchored to a node by key
138    /// (`FloatAnchor::Node`): the sixth layout pass runs only then.
139    pub any_node_float: bool,
140    /// Whether any node declares `wrap_children`.
141    pub any_wrap: bool,
142    /// Whether any node lines its children up by their baselines
143    /// (`cross_align: Baseline`): the layout measures baselines only then.
144    pub any_baseline: bool,
145    /// Whether any node is a table (`LayoutSpec::table`): the column
146    /// alignment in the layout passes runs only on a frame that has one.
147    pub any_table: bool,
148    /// Whether any node is text (a `Text` or `Edit` content).
149    pub any_text: bool,
150    /// Whether any node is a `role="line"` row — what a pointer payload
151    /// inside a key sink is resolved against, so a frame
152    /// without a custom editor never walks a sink's subtree for one.
153    pub any_line: bool,
154
155    /// Scratch for the layout pass's freeze loop (`distribute_run`): one
156    /// byte per in-flow child of the run being resolved, in child order.
157    /// Sized per run and never cleared, so the allocation is made once
158    /// and reused by every run of every frame.
159    pub grow_scratch: Vec<u8>,
160    /// Scratch for the shrink CSS's way (`layout::shrink_as_css`): one
161    /// entry per child of the run giving, made once and reused
162    /// by every overflowing run of every frame, as `grow_scratch` is.
163    pub(crate) shrink_scratch: Vec<crate::layout::Give>,
164    /// Whether any node clips (`clip`, or an overflow that scrolls).
165    pub any_clip: bool,
166    /// Whether any node clips *and* has a radius, so the clip its
167    /// descendants inherit is rounded. Separate from `any_clip`: the
168    /// per-corner bookkeeping is skipped for the ordinary square clip.
169    pub any_rounded_clip: bool,
170    /// Whether any node fades (`opacity` below one).
171    pub any_opacity: bool,
172    /// Whether any node declares `modal`.
173    pub any_modal: bool,
174    /// Whether any node declares `focus_region`.
175    pub any_region: bool,
176    /// Whether any node eases its position (`slide`, or an `enter` with
177    /// an offset) under a transition.
178    pub any_slide: bool,
179    /// Whether any node declares `on_layout`, so the rect report can skip
180    /// the walk.
181    pub any_layout: bool,
182    /// Whether any node declares `on_context_menu`, so a hit region's
183    /// walk for the menu it inherits is skipped wholesale on
184    /// a frame that offers none.
185    pub any_context_menu: bool,
186    /// Some node declared `on_scroll`; emission reads the row per node
187    /// only then.
188    pub any_scroll_handler: bool,
189    /// Some node declared `on_drop`: a hit region's walk for
190    /// the zone it inherits is skipped wholesale on a frame with none.
191    pub any_drop: bool,
192    /// Whether any node declares a workable `exit` (one under a
193    /// transition). Gates the tree swap and the key diff.
194    pub any_exit: bool,
195    /// Whether any node asked for the next frame (`animate`): one node
196    /// asking is the whole window asking.
197    pub any_animate: bool,
198    /// The data index of every node opened with one (`open_indexed`), by
199    /// node. A side list rather than a column, because it is a virtual
200    /// list's rows and nothing else: a frame that builds none is one empty
201    /// `Vec`.
202    ///
203    /// What it is for: a selection endpoint in a row that is *not built*
204    /// can still be ordered against the rows that are, because a row's
205    /// index says where it sits in the data even when nothing on screen
206    /// says where it sits in the frame.
207    pub indexed: Vec<(u32, u64)>,
208    /// How many indexed rows a node's virtual list has, built or not
209    /// (`rowCount`), by node. A side list for the reason `indexed` is one.
210    /// What it is for: Select All inside a `selectable` virtual list is
211    /// the *data*, rows `0..count`, not the rows the frame happened to
212    /// build — and the count is the one thing about the data the core
213    /// cannot see.
214    pub row_counts: Vec<(u32, u64)>,
215    /// The node range every slot fill opened, by the slot's key: `(slot,
216    /// first, end)` over node indices, innermost fill first (a fill
217    /// records itself after the fills inside it). A side list for the
218    /// reason `indexed` is one — a frame with no extension is one empty
219    /// `Vec` — and what stamps `UiEvent::slot`, so a host that fills many
220    /// slots from one extension can route an event by the slot it came
221    /// from without stamping every payload.
222    pub fills: Vec<(Key, u32, u32)>,
223    /// Whether any node declares `selectable`. False on every
224    /// frame of an app that never asks for one, which is what keeps the
225    /// scope walk and the off-screen places of tier 2 off those frames
226    /// entirely.
227    pub any_selectable: bool,
228    /// The box a `FloatConfig::viewport()` float of the host's resolves
229    /// against, in window coordinates: the whole window, or what the
230    /// devtools' dock leaves of it. A zero rect means
231    /// the window. The devtools' own nodes always use the window.
232    pub host_area: crate::geom::Rect,
233}
234
235impl Tree {
236    pub fn new() -> Self {
237        Self::default()
238    }
239
240    pub fn len(&self) -> usize {
241        self.keys.len()
242    }
243
244    pub fn is_empty(&self) -> bool {
245        self.keys.is_empty()
246    }
247
248    /// The index of the node `key` names in this frame, if it is here. A
249    /// linear scan: the one place to swap it for a map if a profile asks.
250    #[inline]
251    pub fn index_of(&self, key: Key) -> Option<usize> {
252        self.keys.iter().position(|k| *k == key)
253    }
254
255    /// The parent an ancestor walk that means "where is this shown"
256    /// takes: the node's parent, except for a float anchored to a node by
257    /// key, whose walk continues from the anchor (`FloatAnchor::Node`).
258    /// `NIL` past the root, and for an anchor the frame does not have.
259    #[inline]
260    pub fn region_parent(&self, i: usize) -> u32 {
261        if self.any_node_float
262            && let Some(crate::spec::FloatConfig {
263                anchor: crate::spec::FloatAnchor::Node(key),
264                ..
265            }) = self.specs[i].layout.float
266        {
267            return self.index_of(key).map_or(NIL, |a| a as u32);
268        }
269        self.parent[i]
270    }
271
272    /// Clears contents but keeps allocations for the next frame.
273    pub fn clear(&mut self) {
274        self.keys.clear();
275        self.origins.clear();
276        self.specs.clear();
277        self.content.clear();
278        self.parent.clear();
279        self.first_child.clear();
280        self.last_child.clear();
281        self.next_sibling.clear();
282        self.size.clear();
283        self.pos.clear();
284        self.scroll_max.clear();
285        self.line.clear();
286        self.baseline.clear();
287        self.any_float = false;
288        self.any_calc_bound = false;
289        self.any_baseline = false;
290        self.any_node_float = false;
291        self.any_wrap = false;
292        self.any_table = false;
293        self.any_text = false;
294        self.any_line = false;
295        self.any_selectable = false;
296        self.any_clip = false;
297        self.any_rounded_clip = false;
298        self.any_opacity = false;
299        self.any_modal = false;
300        self.any_region = false;
301        self.any_slide = false;
302        self.any_layout = false;
303        self.any_context_menu = false;
304        self.any_scroll_handler = false;
305        self.any_drop = false;
306        self.any_exit = false;
307        self.any_animate = false;
308        self.indexed.clear();
309        self.row_counts.clear();
310        self.fills.clear();
311    }
312
313    /// The innermost slot fill node `i` was opened inside, if any.
314    pub fn slot_of(&self, i: usize) -> Option<Key> {
315        let i = i as u32;
316        self.fills
317            .iter()
318            .find(|(_, first, end)| (*first..*end).contains(&i))
319            .map(|(slot, _, _)| *slot)
320    }
321
322    /// Notes what a spec asks of the frame, so a pass whose work exists
323    /// for one feature can skip it when no node declared that feature.
324    /// Called by `push` for every node, and by the root paths that
325    /// replace a spec in place — the one door, so a leaf cannot forget a
326    /// flag a box would have set (an `image` once set two of these and
327    /// painted opaque when it was the frame's only fade).
328    ///
329    /// Each boxed group is tested once, not once per flag it can set: a
330    /// node declaring no events and no animation is done after two null
331    /// checks (C15).
332    #[inline]
333    pub fn note(&mut self, spec: &NodeSpec, content: &NodeContent) {
334        if let Some(f) = spec.layout.float {
335            self.any_float = true;
336            self.any_node_float |= matches!(f.anchor, crate::spec::FloatAnchor::Node(_));
337        }
338        self.any_wrap |= spec.layout.wrap;
339        let l = &spec.layout;
340        self.any_calc_bound |= l.max_w < 0.0
341            || l.max_h < 0.0
342            || l.min_w.as_calc().is_some()
343            || l.min_h.as_calc().is_some();
344        self.any_table |= spec.layout.is_table();
345        self.any_baseline |= spec.layout.cross_align == crate::spec::Align::Baseline;
346        self.any_text |= matches!(
347            content,
348            NodeContent::Text(_) | NodeContent::Edit(_) | NodeContent::Cells(_)
349        );
350        // Through the box rather than through `interact()`: a node that
351        // declares no interaction group is answered by one null check
352        // instead of a read through the empty static (C15).
353        if let Some(i) = spec.interact.as_deref() {
354            self.any_selectable |= i.selectable;
355            self.any_region |= i.focus_region;
356        }
357        if let Some(a) = spec.access.as_deref() {
358            self.any_line |= a.role == Some(crate::access::Role::Line);
359        }
360        if spec.layout.clips() {
361            self.any_clip = true;
362            self.any_rounded_clip |= spec.style.radius != crate::display::SQUARE;
363        }
364        self.any_opacity |= spec.style.opacity < 1.0;
365        self.any_animate |= spec.animate;
366        if let Some(events) = spec.events.as_deref() {
367            self.any_modal |= events.modal.is_some();
368            self.any_layout |= events.on_layout.is_some();
369            self.any_context_menu |= events.on_context_menu.is_some();
370            self.any_scroll_handler |= events.on_scroll.is_some();
371            self.any_drop |= events.on_drop.is_some();
372        }
373        if spec.transition.is_some() {
374            match spec.anim.as_deref() {
375                Some(anim) => {
376                    self.any_slide |= spec.slide || anim.enter.is_some_and(|e| e.offsets());
377                    self.any_exit |= anim.exit.is_some();
378                }
379                None => self.any_slide |= spec.slide,
380            }
381        }
382    }
383
384    #[inline]
385    pub fn push(
386        &mut self,
387        parent: u32,
388        key: Key,
389        origin: OriginId,
390        spec: NodeSpec,
391        content: NodeContent,
392    ) -> u32 {
393        let idx = self.keys.len() as u32;
394        // Read before the move, while the spec is in cache anyway.
395        self.note(&spec, &content);
396        self.keys.push(key);
397        self.origins.push(origin);
398        self.specs.push(spec);
399        self.content.push(content);
400        self.parent.push(parent);
401        self.first_child.push(NIL);
402        self.last_child.push(NIL);
403        self.next_sibling.push(NIL);
404        self.size.push(Size::ZERO);
405        self.pos.push(Vec2::ZERO);
406        self.scroll_max.push(Vec2::ZERO);
407        self.line.push(0);
408
409        if parent != NIL {
410            let p = parent as usize;
411            if self.first_child[p] == NIL {
412                self.first_child[p] = idx;
413            } else {
414                let last = self.last_child[p] as usize;
415                self.next_sibling[last] = idx;
416            }
417            self.last_child[p] = idx;
418        }
419        idx
420    }
421
422    pub fn children(&self, i: u32) -> ChildIter<'_> {
423        ChildIter {
424            tree: self,
425            next: self.first_child[i as usize],
426        }
427    }
428}
429
430pub struct ChildIter<'a> {
431    tree: &'a Tree,
432    next: u32,
433}
434
435impl Iterator for ChildIter<'_> {
436    type Item = u32;
437
438    fn next(&mut self) -> Option<u32> {
439        if self.next == NIL {
440            return None;
441        }
442        let cur = self.next;
443        self.next = self.tree.next_sibling[cur as usize];
444        Some(cur)
445    }
446}
447
448#[cfg(test)]
449mod tests {
450    use super::*;
451
452    #[test]
453    fn sibling_links() {
454        let mut t = Tree::new();
455        let root = t.push(
456            NIL,
457            Key::ROOT,
458            OriginId::HOST,
459            NodeSpec::default(),
460            NodeContent::Container,
461        );
462        let a = t.push(
463            root,
464            Key::ROOT.index(0),
465            OriginId::HOST,
466            NodeSpec::default(),
467            NodeContent::Container,
468        );
469        let a1 = t.push(
470            a,
471            Key::ROOT.index(0).index(0),
472            OriginId::HOST,
473            NodeSpec::default(),
474            NodeContent::Container,
475        );
476        let b = t.push(
477            root,
478            Key::ROOT.index(1),
479            OriginId::HOST,
480            NodeSpec::default(),
481            NodeContent::Container,
482        );
483
484        assert_eq!(t.children(root).collect::<Vec<_>>(), vec![a, b]);
485        assert_eq!(t.children(a).collect::<Vec<_>>(), vec![a1]);
486        assert_eq!(t.children(b).collect::<Vec<_>>(), Vec::<u32>::new());
487        // Preorder invariant: parents precede children.
488        assert!(root < a && a < a1 && a1 < b);
489    }
490}