Skip to main content

frust_core/
tree.rs

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