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