Skip to main content

frust_core/
tree.rs

1//! The retained widget tree, backed by `tree_arena`.
2//!
3//! `tree_arena` gives O(1) node access with simultaneous mutable access to a
4//! node's value and its children. All direct use of the arena is confined to
5//! this module; the rest of the crate speaks in [`WidgetId`]/[`WidgetPod`] terms.
6//!
7//! # Introspection
8//!
9//! [`WidgetTree::roots`] / [`WidgetTree::children`] / [`WidgetTree::inspect`]
10//! make the tree enumerable for read-only tooling. `tree_arena` 0.2's
11//! `root_ids`/`child_ids` iterate a `HashMap` in arbitrary order, so the tree
12//! keeps a parallel insertion-order index (`root_order`/`child_order`) updated
13//! at [`WidgetTree::insert_root`] and [`WidgetTree::insert_child`], used only to
14//! order what the arena reports: the arena stays the source of truth for
15//! membership, so a stale index entry never surfaces a dangling id.
16//!
17//! Containers own children as [`ChildPod`]s, not arena nodes, so
18//! [`WidgetTree::inspect`] walks a node's arena children, then the pods its
19//! widget hands to [`Widget::visit_children`]; [`WidgetTree::children`] stays
20//! arena-scoped. No cycle check is needed: ownership is a tree.
21
22use std::borrow::Cow;
23use std::collections::{HashMap, HashSet};
24
25use kurbo::{Point, Rect, Size};
26use tree_arena::{ArenaMut, ArenaRef, TreeArena};
27
28use crate::view::{ChangeFlags, WidgetId};
29use crate::widget::{ChildPod, Widget};
30
31/// A retained widget plus its layout results and dirty state: where it was placed
32/// (`origin`), how big it is (`size`), and which passes are pending (`flags`).
33pub struct WidgetPod {
34    id: WidgetId,
35    widget: Box<dyn Widget>,
36    origin: Point,
37    size: Size,
38    flags: ChangeFlags,
39    /// The concrete widget type's [`core::any::type_name`], captured once at construction.
40    type_name: &'static str,
41    /// An optional human name for tooling, set via [`WidgetPod::set_debug_label`].
42    debug_label: Option<Cow<'static, str>>,
43}
44
45impl WidgetPod {
46    /// The `type_name` recorded for a pod built from an already type-erased widget.
47    pub const ERASED_TYPE_NAME: &'static str = "<erased dyn Widget>";
48
49    /// Wrap a freshly built, already type-erased widget; it starts dirty for layout
50    /// and paint. [`type_name`](WidgetPod::type_name) reports
51    /// [`ERASED_TYPE_NAME`](WidgetPod::ERASED_TYPE_NAME); prefer [`WidgetPod::new_typed`]
52    /// where the concrete type is in scope.
53    pub fn new(id: WidgetId, widget: Box<dyn Widget>) -> Self {
54        Self::with_type_name(id, widget, Self::ERASED_TYPE_NAME)
55    }
56
57    /// Wrap a freshly built widget whose concrete type is in scope, recording its
58    /// [`core::any::type_name`].
59    ///
60    /// Pass the widget itself, never a `Box<dyn Widget>`: the blanket
61    /// `Widget for Box<dyn Widget>` impl would compile but record the box's type name
62    /// and double-box the widget, breaking the downcast to the view's element type.
63    pub fn new_typed<W: Widget>(id: WidgetId, widget: W) -> Self {
64        Self::with_type_name(id, Box::new(widget), core::any::type_name::<W>())
65    }
66
67    fn with_type_name(id: WidgetId, widget: Box<dyn Widget>, type_name: &'static str) -> Self {
68        Self {
69            id,
70            widget,
71            origin: Point::ZERO,
72            size: Size::ZERO,
73            flags: ChangeFlags::LAYOUT | ChangeFlags::PAINT,
74            type_name,
75            debug_label: None,
76        }
77    }
78
79    /// This pod's stable identity.
80    pub fn id(&self) -> WidgetId {
81        self.id
82    }
83
84    /// The concrete widget type's name, or [`ERASED_TYPE_NAME`](WidgetPod::ERASED_TYPE_NAME).
85    ///
86    /// Recorded at construction, sound because a root pod's element type is fixed by
87    /// `RenderRoot<State, V>`; a swappable [`ChildPod`] asks the live widget
88    /// ([`Widget::type_name`]). Diagnostic only: never parse or match on it.
89    pub fn type_name(&self) -> &'static str {
90        self.type_name
91    }
92
93    /// The human name attached for tooling, if any. `None` by default.
94    pub fn debug_label(&self) -> Option<&str> {
95        self.debug_label.as_deref()
96    }
97
98    /// Attach a human name for tooling; nothing in build/layout/paint reads it.
99    pub fn set_debug_label(&mut self, label: impl Into<Cow<'static, str>>) {
100        self.debug_label = Some(label.into());
101    }
102
103    /// Drop any attached debug label.
104    pub fn clear_debug_label(&mut self) {
105        self.debug_label = None;
106    }
107
108    /// Shared access to the boxed widget.
109    pub fn widget(&self) -> &dyn Widget {
110        &*self.widget
111    }
112
113    /// Mutable access to the boxed widget.
114    pub fn widget_mut(&mut self) -> &mut dyn Widget {
115        &mut *self.widget
116    }
117
118    /// The widget's origin in its parent's coordinate space.
119    pub fn origin(&self) -> Point {
120        self.origin
121    }
122
123    /// The widget's resolved size (valid after a layout pass).
124    pub fn size(&self) -> Size {
125        self.size
126    }
127
128    /// Record the geometry produced by a layout pass.
129    pub fn set_layout(&mut self, origin: Point, size: Size) {
130        self.origin = origin;
131        self.size = size;
132    }
133
134    /// The pending pass flags for this pod.
135    pub fn flags(&self) -> ChangeFlags {
136        self.flags
137    }
138
139    /// Merge additional pending flags (e.g. from a rebuild).
140    pub fn merge_flags(&mut self, flags: ChangeFlags) {
141        self.flags |= flags;
142    }
143
144    /// Clear all pending flags (after the corresponding passes have run).
145    pub fn clear_flags(&mut self) {
146        self.flags = ChangeFlags::NONE;
147    }
148}
149
150/// One node of a read-only widget-tree snapshot, from [`WidgetTree::inspect`].
151///
152/// Plain owned data with no borrow of the tree; geometry is `kurbo` and any wire
153/// format belongs to the consumer.
154#[derive(Debug, Clone, PartialEq)]
155pub struct InspectNode {
156    /// The node's stable [`WidgetId`].
157    pub id: WidgetId,
158    /// The parent's id, or `None` for a root.
159    pub parent: Option<WidgetId>,
160    /// The concrete widget type's name — see [`WidgetPod::type_name`].
161    pub type_name: &'static str,
162    /// The pod's optional debug label — see [`WidgetPod::set_debug_label`].
163    pub debug_label: Option<Cow<'static, str>>,
164    /// The border box in absolute window coordinates (logical px), accumulated from
165    /// the root; zero-sized until a layout pass has run.
166    pub bounds: Rect,
167    /// This node's children: arena children first, then the widget's owned [`ChildPod`]s.
168    pub children: Vec<WidgetId>,
169    /// Distance from the root (roots are `0`).
170    pub depth: usize,
171}
172
173/// The widget tree: a thin, typed wrapper over `tree_arena::TreeArena`.
174pub struct WidgetTree {
175    arena: TreeArena<WidgetPod>,
176    /// Roots in insertion order. Ordering only — the arena owns membership.
177    root_order: Vec<WidgetId>,
178    /// Per-parent child ids in insertion order. Same rule: ordering only.
179    child_order: HashMap<WidgetId, Vec<WidgetId>>,
180}
181
182impl WidgetTree {
183    /// Create an empty tree.
184    pub fn new() -> Self {
185        Self {
186            arena: TreeArena::new(),
187            root_order: Vec::new(),
188            child_order: HashMap::new(),
189        }
190    }
191
192    /// Insert `pod` as a root of the tree, returning its id.
193    pub fn insert_root(&mut self, pod: WidgetPod) -> WidgetId {
194        let id = pod.id();
195        self.arena.roots_mut().insert(id.0, pod);
196        self.root_order.push(id);
197        id
198    }
199
200    /// Insert `pod` as a child of `parent`; `None` if `parent` is not in the tree.
201    pub fn insert_child(&mut self, parent: WidgetId, pod: WidgetPod) -> Option<WidgetId> {
202        let id = pod.id();
203        let mut parent_mut: ArenaMut<'_, WidgetPod> = self.arena.find_mut(parent.0)?;
204        parent_mut.children.insert(id.0, pod);
205        self.child_order.entry(parent).or_default().push(id);
206        Some(id)
207    }
208
209    /// Shared access to the pod with the given id, anywhere in the tree.
210    pub fn pod(&self, id: WidgetId) -> Option<&WidgetPod> {
211        self.arena.find(id.0).map(|node| node.item)
212    }
213
214    /// Mutable access to the pod with the given id, anywhere in the tree.
215    pub fn pod_mut(&mut self, id: WidgetId) -> Option<&mut WidgetPod> {
216        self.arena.find_mut(id.0).map(|node| node.item)
217    }
218
219    /// The tree's root ids, in insertion order.
220    pub fn roots(&self) -> Vec<WidgetId> {
221        let live: HashSet<u64> = self.arena.root_ids().collect();
222        order_against(&self.root_order, &live)
223    }
224
225    /// The children of `id` in insertion order; empty if `id` is absent or childless.
226    pub fn children(&self, id: WidgetId) -> Vec<WidgetId> {
227        match self.arena.find(id.0) {
228            Some(node) => self.ordered_children(id, node),
229            None => Vec::new(),
230        }
231    }
232
233    /// A read-only pre-order snapshot of the whole retained tree (siblings in
234    /// declaration order).
235    ///
236    /// Covers both child mechanisms: arena children, then the [`ChildPod`]s a widget
237    /// publishes through [`Widget::visit_children`] (a container that does not
238    /// override it reads as a leaf). Bounds come from the last layout pass, so call
239    /// it after one or the rects are zero-sized.
240    pub fn inspect(&self) -> Vec<InspectNode> {
241        let mut out = Vec::new();
242        let roots = self.arena.roots();
243        for id in self.roots() {
244            if let Some(node) = roots.into_item(id.0) {
245                self.inspect_node(node, id, None, Point::ZERO, 0, &mut out);
246            }
247        }
248        out
249    }
250
251    /// Per-node layout-dirty state for tooling and tests: one `(id, layout_dirty)`
252    /// pair per node [`WidgetTree::inspect`] reports, in its order, so the two zip.
253    ///
254    /// A [`ChildPod`] reports [`ChildPod::is_layout_dirty`]; an arena [`WidgetPod`]
255    /// always reports `false` (the root lays it out unconditionally). Kept beside
256    /// `inspect` so the plain [`InspectNode`] shape does not change.
257    pub fn inspect_layout_dirty(&self) -> Vec<(WidgetId, bool)> {
258        let mut out = Vec::new();
259        let roots = self.arena.roots();
260        for id in self.roots() {
261            if let Some(node) = roots.into_item(id.0) {
262                self.layout_dirty_node(node, id, &mut out);
263            }
264        }
265        out
266    }
267
268    /// Per-node paint-dirty state: the twin of [`WidgetTree::inspect_layout_dirty`]
269    /// reporting [`ChildPod::is_paint_dirty`]; an arena [`WidgetPod`] reports `false`.
270    pub fn inspect_paint_dirty(&self) -> Vec<(WidgetId, bool)> {
271        let mut out = Vec::new();
272        let roots = self.arena.roots();
273        for id in self.roots() {
274            if let Some(node) = roots.into_item(id.0) {
275                self.pod_flag_node(node, id, &ChildPod::is_paint_dirty, &mut out);
276            }
277        }
278        out
279    }
280
281    /// [`WidgetTree::inspect_paint_dirty`]'s arena step.
282    fn pod_flag_node(
283        &self,
284        node: ArenaRef<'_, WidgetPod>,
285        id: WidgetId,
286        flag: &dyn Fn(&ChildPod) -> bool,
287        out: &mut Vec<(WidgetId, bool)>,
288    ) {
289        out.push((id, false));
290        for child_id in self.ordered_children(id, node) {
291            if let Some(child) = node.children.into_item(child_id.0) {
292                self.pod_flag_node(child, child_id, flag, out);
293            }
294        }
295        Self::pod_flag_pods(node.item.widget(), flag, out);
296    }
297
298    /// [`WidgetTree::inspect_paint_dirty`]'s pod step.
299    fn pod_flag_pods(
300        widget: &dyn Widget,
301        flag: &dyn Fn(&ChildPod) -> bool,
302        out: &mut Vec<(WidgetId, bool)>,
303    ) {
304        widget.visit_children(&mut |child| {
305            out.push((child.inspect_id(), flag(child)));
306            Self::pod_flag_pods(child.widget(), flag, out);
307        });
308    }
309
310    /// [`WidgetTree::inspect_layout_dirty`]'s arena step, in [`WidgetTree::inspect_node`]'s order.
311    fn layout_dirty_node(
312        &self,
313        node: ArenaRef<'_, WidgetPod>,
314        id: WidgetId,
315        out: &mut Vec<(WidgetId, bool)>,
316    ) {
317        out.push((id, false));
318        for child_id in self.ordered_children(id, node) {
319            if let Some(child) = node.children.into_item(child_id.0) {
320                self.layout_dirty_node(child, child_id, out);
321            }
322        }
323        Self::layout_dirty_pods(node.item.widget(), out);
324    }
325
326    /// [`WidgetTree::inspect_layout_dirty`]'s pod step.
327    fn layout_dirty_pods(widget: &dyn Widget, out: &mut Vec<(WidgetId, bool)>) {
328        widget.visit_children(&mut |child| {
329            out.push((child.inspect_id(), child.is_layout_dirty()));
330            Self::layout_dirty_pods(child.widget(), out);
331        });
332    }
333
334    /// Push `node`'s snapshot, then recurse into its arena children and its widget's
335    /// owned pods. `parent_origin` is the parent's absolute origin.
336    fn inspect_node(
337        &self,
338        node: ArenaRef<'_, WidgetPod>,
339        id: WidgetId,
340        parent: Option<WidgetId>,
341        parent_origin: Point,
342        depth: usize,
343        out: &mut Vec<InspectNode>,
344    ) {
345        let pod = node.item;
346        let origin = parent_origin + pod.origin().to_vec2();
347        let arena_children = self.ordered_children(id, node);
348        // Reserve this node's slot before descending, so pre-order holds.
349        let slot = out.len();
350        out.push(InspectNode {
351            id,
352            parent,
353            type_name: pod.type_name(),
354            debug_label: pod.debug_label.clone(),
355            bounds: Rect::from_origin_size(origin, pod.size()),
356            children: Vec::new(),
357            depth,
358        });
359        let mut children = Vec::with_capacity(arena_children.len());
360        for child_id in arena_children {
361            if let Some(child) = node.children.into_item(child_id.0) {
362                children.push(child_id);
363                self.inspect_node(child, child_id, Some(id), origin, depth + 1, out);
364            }
365        }
366        Self::inspect_pods(pod.widget(), id, origin, depth, &mut children, out);
367        out[slot].children = children;
368    }
369
370    /// Push a snapshot for each of `widget`'s owned [`ChildPod`]s and their subtrees.
371    fn inspect_pods(
372        widget: &dyn Widget,
373        parent: WidgetId,
374        parent_origin: Point,
375        parent_depth: usize,
376        children: &mut Vec<WidgetId>,
377        out: &mut Vec<InspectNode>,
378    ) {
379        widget.visit_children(&mut |child| {
380            let child_id = child.inspect_id();
381            children.push(child_id);
382            Self::inspect_child(
383                child,
384                child_id,
385                parent,
386                parent_origin,
387                parent_depth + 1,
388                out,
389            );
390        });
391    }
392
393    /// The [`ChildPod`] mirror of [`WidgetTree::inspect_node`].
394    fn inspect_child(
395        child: &ChildPod,
396        id: WidgetId,
397        parent: WidgetId,
398        parent_origin: Point,
399        depth: usize,
400        out: &mut Vec<InspectNode>,
401    ) {
402        let origin = parent_origin + child.origin().to_vec2();
403        let slot = out.len();
404        out.push(InspectNode {
405            id,
406            parent: Some(parent),
407            type_name: child.type_name(),
408            debug_label: child.debug_label_cow(),
409            bounds: Rect::from_origin_size(origin, child.size()),
410            children: Vec::new(),
411            depth,
412        });
413        let mut grandchildren = Vec::new();
414        Self::inspect_pods(child.widget(), id, origin, depth, &mut grandchildren, out);
415        out[slot].children = grandchildren;
416    }
417
418    /// The arena's children of `node`, ordered by the recorded insertion index.
419    fn ordered_children(&self, id: WidgetId, node: ArenaRef<'_, WidgetPod>) -> Vec<WidgetId> {
420        let live: HashSet<u64> = node.child_ids().into_iter().collect();
421        let recorded = self.child_order.get(&id).map_or(&[][..], Vec::as_slice);
422        order_against(recorded, &live)
423    }
424}
425
426/// Order `live` (the arena's authoritative id set) by `recorded` (the insertion
427/// index). A recorded id the arena dropped is skipped and an unrecorded live id is
428/// appended in id order, so traversal stays truthful if a mutation path forgets
429/// the index.
430fn order_against(recorded: &[WidgetId], live: &HashSet<u64>) -> Vec<WidgetId> {
431    let mut ordered: Vec<WidgetId> = recorded
432        .iter()
433        .copied()
434        .filter(|id| live.contains(&id.0))
435        .collect();
436    if ordered.len() != live.len() {
437        let seen: HashSet<u64> = ordered.iter().map(|id| id.0).collect();
438        let mut unrecorded: Vec<u64> = live.difference(&seen).copied().collect();
439        unrecorded.sort_unstable();
440        ordered.extend(unrecorded.into_iter().map(WidgetId));
441    }
442    ordered
443}
444
445impl Default for WidgetTree {
446    fn default() -> Self {
447        Self::new()
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use super::*;
454    use crate::layout::BoxConstraints;
455    use crate::widget::{LayoutCtx, PaintCtx, PaintScene};
456
457    struct Leaf;
458    impl Widget for Leaf {
459        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
460            bc.max()
461        }
462        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
463    }
464
465    /// A container in the shape every real one has: children owned as
466    /// [`ChildPod`]s, published through [`Widget::visit_children`].
467    struct Container {
468        children: Vec<ChildPod>,
469    }
470
471    impl Container {
472        /// A container over `children`, each placed at `origin` with `size` the
473        /// way a layout pass would have left it.
474        fn new(children: Vec<(Point, Size, Box<dyn Widget>)>) -> Self {
475            Self {
476                children: children
477                    .into_iter()
478                    .map(|(origin, size, widget)| {
479                        let mut pod = ChildPod::new(widget);
480                        pod.set_origin(origin);
481                        // A pod records its size from `layout_child`; these tests
482                        // assert the walk, not layout, so drive it directly.
483                        pod.layout_child(&mut LayoutCtx::new(), &BoxConstraints::new(size, size));
484                        pod
485                    })
486                    .collect(),
487            }
488        }
489    }
490
491    impl Widget for Container {
492        fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
493            bc.max()
494        }
495        fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
496        fn visit_children(&self, visitor: &mut dyn FnMut(&ChildPod)) {
497            for child in &self.children {
498                visitor(child);
499            }
500        }
501    }
502
503    fn pod(id: u64) -> WidgetPod {
504        WidgetPod::new(WidgetId(id), Box::new(Leaf))
505    }
506
507    #[test]
508    fn insert_root_and_find() {
509        let mut tree = WidgetTree::new();
510        let id = tree.insert_root(pod(1));
511        assert_eq!(id, WidgetId(1));
512        assert!(tree.pod(id).is_some());
513        assert!(tree.pod_mut(id).is_some());
514        assert!(tree.pod(WidgetId(999)).is_none());
515    }
516
517    #[test]
518    fn insert_child_traverses_arena() {
519        let mut tree = WidgetTree::new();
520        let root = tree.insert_root(pod(1));
521        let child = tree.insert_child(root, pod(2)).expect("child inserted");
522        assert_eq!(child, WidgetId(2));
523        // The child is reachable via the arena's global find (descendant lookup).
524        assert!(tree.pod(child).is_some());
525        // Inserting under a missing parent fails cleanly.
526        assert!(tree.insert_child(WidgetId(42), pod(3)).is_none());
527    }
528
529    #[test]
530    fn roots_and_children_follow_insertion_order() {
531        let mut tree = WidgetTree::new();
532        // Two roots, inserted high id first: recorded order, not id order.
533        let root_b = tree.insert_root(pod(20));
534        let root_a = tree.insert_root(pod(10));
535        assert_eq!(tree.roots(), vec![root_b, root_a]);
536
537        let c2 = tree.insert_child(root_b, pod(22)).unwrap();
538        let c1 = tree.insert_child(root_b, pod(21)).unwrap();
539        let grandchild = tree.insert_child(c2, pod(30)).unwrap();
540        assert_eq!(tree.children(root_b), vec![c2, c1]);
541        assert_eq!(tree.children(c2), vec![grandchild]);
542        // Leaves and unknown ids both report no children, never a dangling id.
543        assert!(tree.children(c1).is_empty());
544        assert!(tree.children(WidgetId(999)).is_empty());
545    }
546
547    #[test]
548    fn pod_records_type_name_and_optional_label() {
549        let mut tree = WidgetTree::new();
550        // The erased constructor cannot name the concrete type.
551        let erased = tree.insert_root(WidgetPod::new(WidgetId(1), Box::new(Leaf)));
552        assert_eq!(
553            tree.pod(erased).unwrap().type_name(),
554            WidgetPod::ERASED_TYPE_NAME
555        );
556
557        let typed = tree
558            .insert_child(erased, WidgetPod::new_typed(WidgetId(2), Leaf))
559            .unwrap();
560        let pod = tree.pod_mut(typed).unwrap();
561        assert!(pod.type_name().ends_with("Leaf"), "{}", pod.type_name());
562        // Labels default to absent, are set on demand, and clear again.
563        assert_eq!(pod.debug_label(), None);
564        pod.set_debug_label("the-leaf");
565        assert_eq!(pod.debug_label(), Some("the-leaf"));
566        pod.clear_debug_label();
567        assert_eq!(pod.debug_label(), None);
568    }
569
570    #[test]
571    fn inspect_walks_pre_order_with_absolute_bounds() {
572        let mut tree = WidgetTree::new();
573        let root = tree.insert_root(WidgetPod::new_typed(WidgetId(1), Leaf));
574        let a = tree
575            .insert_child(root, WidgetPod::new_typed(WidgetId(2), Leaf))
576            .unwrap();
577        let a_child = tree
578            .insert_child(a, WidgetPod::new_typed(WidgetId(3), Leaf))
579            .unwrap();
580        let b = tree
581            .insert_child(root, WidgetPod::new_typed(WidgetId(4), Leaf))
582            .unwrap();
583
584        // Geometry as a layout pass would record it: origins parent-relative.
585        tree.pod_mut(root)
586            .unwrap()
587            .set_layout(Point::ZERO, Size::new(100.0, 100.0));
588        tree.pod_mut(a)
589            .unwrap()
590            .set_layout(Point::new(10.0, 5.0), Size::new(50.0, 40.0));
591        tree.pod_mut(a_child)
592            .unwrap()
593            .set_layout(Point::new(2.0, 3.0), Size::new(10.0, 10.0));
594        tree.pod_mut(b)
595            .unwrap()
596            .set_layout(Point::new(0.0, 60.0), Size::new(20.0, 20.0));
597        tree.pod_mut(a).unwrap().set_debug_label("branch-a");
598
599        let nodes = tree.inspect();
600        // Pre-order: root, a, a's child, b.
601        let ids: Vec<WidgetId> = nodes.iter().map(|n| n.id).collect();
602        assert_eq!(ids, vec![root, a, a_child, b]);
603        assert_eq!(
604            nodes.iter().map(|n| n.depth).collect::<Vec<_>>(),
605            vec![0, 1, 2, 1]
606        );
607        assert_eq!(
608            nodes.iter().map(|n| n.parent).collect::<Vec<_>>(),
609            vec![None, Some(root), Some(a), Some(root)]
610        );
611        assert_eq!(nodes[0].children, vec![a, b]);
612        assert_eq!(nodes[2].children, Vec::new());
613
614        // Bounds accumulate down: a's child sits at 10+2, 5+3.
615        assert_eq!(nodes[1].bounds, Rect::new(10.0, 5.0, 60.0, 45.0));
616        assert_eq!(nodes[2].bounds, Rect::new(12.0, 8.0, 22.0, 18.0));
617
618        // Type names captured, labels default to None.
619        assert!(nodes[0].type_name.ends_with("Leaf"));
620        assert_eq!(nodes[1].debug_label.as_deref(), Some("branch-a"));
621        assert!(nodes[0].debug_label.is_none());
622        assert!(nodes[3].debug_label.is_none());
623    }
624
625    #[test]
626    fn inspect_of_an_unlaid_out_tree_reports_zero_rects() {
627        let mut tree = WidgetTree::new();
628        let root = tree.insert_root(WidgetPod::new_typed(WidgetId(1), Leaf));
629        tree.insert_child(root, WidgetPod::new_typed(WidgetId(2), Leaf))
630            .unwrap();
631        let nodes = tree.inspect();
632        assert_eq!(nodes.len(), 2);
633        assert!(nodes.iter().all(|n| n.bounds == Rect::ZERO));
634    }
635
636    #[test]
637    fn traversal_follows_the_arena_not_the_order_index() {
638        // The order index is ordering only: the arena decides membership. Both
639        // divergence directions are covered, since `WidgetTree` exposes no
640        // removal today and a future one must not be able to leak a stale id.
641        let mut tree = WidgetTree::new();
642        let root = tree.insert_root(pod(1));
643        let kept = tree.insert_child(root, pod(2)).unwrap();
644
645        // An id the index records but the arena never held is dropped.
646        tree.child_order
647            .get_mut(&root)
648            .unwrap()
649            .push(WidgetId(1234));
650        assert_eq!(tree.children(root), vec![kept]);
651        assert_eq!(tree.inspect().len(), 2);
652
653        // An arena child the index never saw is still reported (id order).
654        tree.child_order.remove(&root);
655        assert_eq!(tree.children(root), vec![kept]);
656        tree.root_order.clear();
657        assert_eq!(tree.roots(), vec![root]);
658    }
659
660    #[test]
661    fn inspect_descends_through_child_pods() {
662        // The shape the arena alone cannot see: root -> container -> (leaf,
663        // nested container -> leaf). Only the root is an arena node; everything
664        // below it is a `ChildPod` reached through `visit_children`.
665        let inner = Container::new(vec![(
666            Point::new(1.0, 1.0),
667            Size::new(5.0, 5.0),
668            Box::new(Leaf),
669        )]);
670        let outer = Container::new(vec![
671            (Point::new(10.0, 5.0), Size::new(50.0, 40.0), Box::new(Leaf)),
672            (
673                Point::new(0.0, 60.0),
674                Size::new(20.0, 20.0),
675                Box::new(inner) as Box<dyn Widget>,
676            ),
677        ]);
678
679        let mut tree = WidgetTree::new();
680        let root = tree.insert_root(WidgetPod::new_typed(WidgetId(1), outer));
681        tree.pod_mut(root)
682            .unwrap()
683            .set_layout(Point::new(2.0, 3.0), Size::new(100.0, 100.0));
684
685        let nodes = tree.inspect();
686        assert_eq!(nodes.len(), 4, "root + two pods + one grandchild pod");
687
688        // Pre-order: root, first pod, second pod, the nested pod under it.
689        assert_eq!(
690            nodes.iter().map(|n| n.depth).collect::<Vec<_>>(),
691            vec![0, 1, 1, 2],
692            "a pod's depth continues from its owning node's"
693        );
694        assert_eq!(nodes[0].parent, None);
695        assert_eq!(nodes[1].parent, Some(nodes[0].id));
696        assert_eq!(nodes[2].parent, Some(nodes[0].id));
697        assert_eq!(nodes[3].parent, Some(nodes[2].id));
698        assert_eq!(nodes[0].children, vec![nodes[1].id, nodes[2].id]);
699        assert_eq!(nodes[2].children, vec![nodes[3].id]);
700        assert!(nodes[1].children.is_empty(), "a leaf publishes no children");
701
702        // Bounds accumulate through ChildPod origins, on top of the arena pod's
703        // own absolute origin: root at (2,3); first pod at (2+10, 3+5); the
704        // nested container at (2+0, 3+60); its child at (2+0+1, 3+60+1).
705        assert_eq!(nodes[0].bounds, Rect::new(2.0, 3.0, 102.0, 103.0));
706        assert_eq!(nodes[1].bounds, Rect::new(12.0, 8.0, 62.0, 48.0));
707        assert_eq!(nodes[2].bounds, Rect::new(2.0, 63.0, 22.0, 83.0));
708        assert_eq!(nodes[3].bounds, Rect::new(3.0, 64.0, 8.0, 69.0));
709
710        // Type names: the arena pod's is captured by `new_typed`, a child pod's
711        // by its construction path.
712        assert!(
713            nodes[0].type_name.ends_with("Container"),
714            "{}",
715            nodes[0].type_name
716        );
717        assert!(
718            nodes[1..]
719                .iter()
720                .all(|n| n.type_name.ends_with("Leaf") || n.type_name.ends_with("Container"))
721        );
722
723        // Ids are unique, and every pod id is drawn from the reserved range so
724        // it can never be mistaken for an arena `WidgetId`.
725        let ids: HashSet<u64> = nodes.iter().map(|n| n.id.0).collect();
726        assert_eq!(ids.len(), nodes.len(), "ids are unique across a snapshot");
727        assert!(
728            nodes[1..]
729                .iter()
730                .all(|n| n.id.0 >= crate::widget::ChildPod::INSPECT_ID_BASE)
731        );
732    }
733
734    #[test]
735    fn a_pods_tooling_id_and_label_survive_the_next_walk() {
736        // Selection stability: a devtools client holding an id must still be
737        // holding the same pod on the next frame's snapshot.
738        let mut tree = WidgetTree::new();
739        let root = tree.insert_root(WidgetPod::new_typed(
740            WidgetId(1),
741            Container::new(vec![(Point::ZERO, Size::new(4.0, 4.0), Box::new(Leaf))]),
742        ));
743        let first = tree.inspect();
744        let second = tree.inspect();
745        assert_eq!(first[1].id, second[1].id);
746
747        // A label attached to a child pod reaches the snapshot, and clearing it
748        // takes it back out.
749        let pod = tree.pod_mut(root).unwrap();
750        let container = pod
751            .widget_mut()
752            .downcast_mut::<Container>()
753            .expect("the root widget is the container");
754        container.children[0].set_debug_label("the-child");
755        assert_eq!(tree.inspect()[1].debug_label.as_deref(), Some("the-child"));
756        let container = tree
757            .pod_mut(root)
758            .unwrap()
759            .widget_mut()
760            .downcast_mut::<Container>()
761            .expect("the root widget is the container");
762        container.children[0].clear_debug_label();
763        assert_eq!(tree.inspect()[1].debug_label, None);
764    }
765
766    #[test]
767    fn a_widget_that_ignores_the_seam_reads_as_a_leaf() {
768        // The default impl is free to ignore: a container that never overrides
769        // `visit_children` contributes exactly one node, no matter what it owns.
770        struct Opaque {
771            _child: ChildPod,
772        }
773        impl Widget for Opaque {
774            fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
775                bc.max()
776            }
777            fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
778        }
779
780        let mut tree = WidgetTree::new();
781        tree.insert_root(WidgetPod::new_typed(
782            WidgetId(1),
783            Opaque {
784                _child: ChildPod::new(Box::new(Leaf)),
785            },
786        ));
787        assert_eq!(tree.inspect().len(), 1);
788    }
789
790    #[test]
791    fn pod_stores_layout_geometry() {
792        let mut tree = WidgetTree::new();
793        let id = tree.insert_root(pod(1));
794        let p = tree.pod_mut(id).unwrap();
795        assert!(p.flags().needs_layout());
796        p.set_layout(Point::new(2.0, 3.0), Size::new(10.0, 20.0));
797        p.clear_flags();
798        assert_eq!(p.origin(), Point::new(2.0, 3.0));
799        assert_eq!(p.size(), Size::new(10.0, 20.0));
800        assert!(p.flags().is_empty());
801    }
802}