smudge_core/tree.rs
1//! The retained widget tree.
2//!
3//! The tree stores one node per widget, keyed by a generational
4//! [`WidgetId`] that stays valid for the node's lifetime. It keeps its own
5//! parent and child links in step with the layout engine's tree, gives every
6//! node a reactive owner, and records how much of each node is out of date.
7//!
8//! The tree is the only owner of widget state. Effects never hold a reference
9//! to it: a binding pushes an update onto a queue and the frame drains that
10//! queue, so every mutation of the tree happens in one place and in one order.
11
12use std::any::Any;
13use std::collections::{HashMap, HashSet};
14use std::rc::Rc;
15
16use slotmap::{Key, SlotMap, new_key_type};
17
18use crate::geometry::{Point, Rect, Size};
19use crate::paint::PaintOp;
20use crate::reactive::{Owner, RwSignal};
21use crate::semantics::{A11yChanges, Role, SemanticsNode};
22use crate::style::{LayoutProps, PaintProps, Style};
23use crate::theme::Theme;
24use crate::variants::{Variants, resolve};
25use crate::widget::Widget;
26
27#[path = "display_list_pass.rs"]
28mod display_list_pass;
29#[path = "focus.rs"]
30pub(crate) mod focus;
31#[path = "interaction.rs"]
32pub(crate) mod interaction;
33#[path = "layout.rs"]
34mod layout;
35#[path = "paint_pass.rs"]
36mod paint_pass;
37
38use self::interaction::{Handlers, Interaction};
39
40new_key_type! {
41 /// Identifies one node in the widget tree.
42 ///
43 /// The id stays valid for as long as the node lives and is never reused for
44 /// a different node, so an id whose node was removed refers to nothing.
45 /// Every operation that takes one reports that rather than acting on
46 /// another node.
47 pub struct WidgetId;
48}
49
50impl WidgetId {
51 /// Returns the raw representation of the id.
52 ///
53 /// An accessibility backend identifies its nodes by `u64`, so the
54 /// projection maps ids through this conversion. The raw value of two
55 /// live ids is always different, and a value this tree never handed out
56 /// restores to an id that names no node.
57 pub fn as_u64(self) -> u64 {
58 self.data().as_ffi()
59 }
60
61 /// Restores the id that [`WidgetId::as_u64`] encoded.
62 ///
63 /// An id whose node was removed already, or one that names no node at
64 /// all, is a no-op everywhere the tree takes it.
65 pub fn from_u64(raw: u64) -> WidgetId {
66 WidgetId(slotmap::KeyData::from_ffi(raw))
67 }
68}
69
70/// How much of a node is out of date.
71///
72/// The variants are ordered, and [`Dirty::Layout`] implies [`Dirty::Paint`]. A
73/// node's flag only ever rises within a frame.
74#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
75pub enum Dirty {
76 /// Nothing about the node changed since the last frame.
77 Clean,
78 /// The node's paint output must be regenerated. Its layout is current.
79 Paint,
80 /// The node must be laid out again, which repaints it as well.
81 Layout,
82}
83
84/// The bounds a node painted in the last frame, and the clip it painted under.
85///
86/// The bounds are the union of the node's paint operation bounds before
87/// clipping, in the paint coordinates the node painted at, which are its
88/// window coordinates translated by `shift`. The clip is kept beside them,
89/// because damage converts the two to physical pixels separately: a pixel
90/// that a clip edge only partly covers is still drawn, and clipping the
91/// bounds in logical space first would miss it.
92#[derive(Copy, Clone, Debug, PartialEq)]
93struct PaintedBounds {
94 /// The union of the node's paint operation bounds, before clipping.
95 bounds: Rect,
96 /// The rectangle the node's subtree was clipped to, in window coordinates.
97 clip: Rect,
98 /// The paint shift the bounds are relative to, so damage can turn them
99 /// back into window coordinates.
100 shift: Point,
101}
102
103impl PaintedBounds {
104 /// Returns the bounds in window coordinates.
105 fn window_bounds(&self) -> Rect {
106 self.bounds.translated(Point {
107 x: -self.shift.x,
108 y: -self.shift.y,
109 })
110 }
111}
112
113/// One widget and everything the frame needs to know about it.
114struct Node {
115 /// The widget this node owns.
116 widget: Box<dyn Widget>,
117 /// The node this one is a child of, or `None` for the root.
118 parent: Option<WidgetId>,
119 /// The node's children, in paint order.
120 children: Vec<WidgetId>,
121 /// The node's base style, written by the element's builders and their
122 /// bindings.
123 base: Style,
124 /// The style layout and paint read, with the patches of the active
125 /// interaction states applied over the base style.
126 resolved: Style,
127 /// The style patches per interaction state, when the element declared
128 /// any. Without patches the resolved style tracks the base style.
129 variants: Option<Rc<Variants>>,
130 /// Whether the widget's last paint resolved a value from the theme, so a
131 /// theme switch repaints the node.
132 uses_tokens: bool,
133 /// The node's id in the layout engine's tree.
134 taffy: taffy::NodeId,
135 /// The reactive owner that every binding of this node runs under.
136 ///
137 /// Cleaning the owner up stops the node's bindings, so removal cleans it
138 /// up and nothing else has to track binding handles.
139 owner: Owner,
140 /// How much of the node is out of date.
141 dirty: Dirty,
142 /// The node's absolute rectangle, set by the walk that computes absolute
143 /// rectangles, which the layout pass and a scroll offset change run.
144 rect: Rect,
145 /// The node's content box: its rectangle inside the border and the
146 /// padding, set by the same walk.
147 content: Rect,
148 /// The translation from the node's window coordinates to its paint
149 /// coordinates, which is the sum of the scroll offsets that move it.
150 ///
151 /// The walk that computes absolute rectangles maintains it: a flowing
152 /// child adds the scroll offset of its parent to the parent's shift, and
153 /// an absolutely positioned child inherits the parent's shift. The paint
154 /// pass stores operations and painted bounds in paint coordinates, which
155 /// lets a scroll move content by changing offsets alone: the display list
156 /// wraps the moved children in an offset scope instead of repainting
157 /// them, and damage translates the bounds back with the shift.
158 paint_shift: Point,
159 /// The node's rectangle in paint coordinates, `rect` translated by
160 /// `paint_shift`.
161 ///
162 /// The walk computes it from the layout without the scroll offsets, so a
163 /// scroll leaves it exactly as it was and the walk can tell a node that
164 /// only scrolled from one that has to paint again.
165 paint_rect: Rect,
166 /// The node's content box in paint coordinates, `content` translated by
167 /// `paint_shift`.
168 paint_content: Rect,
169 /// The rectangle the node's subtree is clipped to, set by the same walk.
170 clip: Rect,
171 /// The translation applied to the node's flowing children after layout,
172 /// outside the layout engine. It is how a scroll view moves its content:
173 /// the walk that computes absolute rectangles adds it to the paint shift
174 /// of every child that stays in the flow, which moves the child's window
175 /// rectangle by minus the offset, and [`WidgetTree::set_scroll_offset`]
176 /// re-runs that walk for the subtree alone. An absolutely positioned
177 /// child is left where its insets place it.
178 scroll_offset: Point,
179 /// The signal that pins the node's scroll offset to the end of its
180 /// flowing content while it reads `true`, set by the element's builders.
181 /// While it does, [`WidgetTree::layout`] pins the offset after each pass;
182 /// see the resolution in the layout pass module.
183 follow: Option<RwSignal<bool>>,
184 /// The flowing child the node keeps in place, by index, and how far
185 /// below the top of its content box, set by the element's builders.
186 /// [`WidgetTree::layout`] applies it after each pass.
187 pin: Option<(usize, f32)>,
188 /// The node's interaction state: hovered, pressed, disabled and focus.
189 interaction: Interaction,
190 /// The cursor the node's element set for the pointer over it, or `None`
191 /// when the node leaves the choice to an ancestor or the platform.
192 cursor: Option<crate::window::Cursor>,
193 /// The input handlers the node owns, set by its element's builders.
194 handlers: Handlers,
195 /// The role the element's builders set for the accessibility projection,
196 /// which overrides the role the widget reports.
197 a11y_role: Option<Role>,
198 /// The accessible label the element's builders set, which overrides the
199 /// label the widget reports.
200 a11y_label: Option<String>,
201 /// The node's cached paint output, filled by the paint pass.
202 ops: Vec<PaintOp>,
203 /// The bounds the node painted last frame and the clip it painted under.
204 painted: Option<PaintedBounds>,
205}
206
207/// Returns whether two child lists that hold the same nodes in a different
208/// relative order restack any pair of them.
209///
210/// Children one list gained or lost are skipped: a mount and a removal move
211/// nothing that stayed, so their damage comes from the nodes themselves. Two
212/// survivors that traded places can overlap, and no rectangle or dirty flag
213/// announces the pixels they now share in the other order.
214fn children_differ_in_order(before: &[WidgetId], after: &[WidgetId]) -> bool {
215 let kept_after: HashSet<WidgetId> = after.iter().copied().collect();
216 let kept_before: HashSet<WidgetId> = before.iter().copied().collect();
217 let survivors_of_before: Vec<WidgetId> = before
218 .iter()
219 .filter(|id| kept_after.contains(id))
220 .copied()
221 .collect();
222 let survivors_of_after: Vec<WidgetId> = after
223 .iter()
224 .filter(|id| kept_before.contains(id))
225 .copied()
226 .collect();
227 survivors_of_before != survivors_of_after
228}
229
230/// How many pending accessibility changes the tree queues before the next
231/// projection rebuilds from scratch instead.
232const A11Y_DIRTY_CAP: usize = 4096;
233
234/// A mounted focus trap, and the focus to restore when it ends.
235#[derive(Clone, Copy)]
236struct FocusTrap {
237 /// The node whose subtree keeps the keyboard.
238 node: WidgetId,
239 /// The node that held the focus when the trap last became able to hold
240 /// it, by mounting visible or by showing again.
241 restore: Option<WidgetId>,
242 /// Whether the trap could hold the focus when it was last looked at, so
243 /// a change between showing and hiding is seen once.
244 active: bool,
245}
246
247/// The retained tree of widgets.
248///
249/// A tree has at most one root. Removing the root leaves the tree empty.
250pub(crate) struct WidgetTree {
251 /// The nodes, keyed by the id the tree handed out.
252 nodes: SlotMap<WidgetId, Node>,
253 /// The layout engine's tree, holding one node per widget node.
254 taffy: taffy::TaffyTree<WidgetId>,
255 /// The root node, when the tree has one.
256 root: Option<WidgetId>,
257 /// Nodes that need painting, in the order they were first marked.
258 paint_queue: Vec<WidgetId>,
259 /// Whether any node needs a new layout pass.
260 layout_dirty: bool,
261 /// The painted bounds the next frame must repaint: the nodes removed
262 /// since the last frame, and the subtrees a child reorder restacked.
263 pending_damage: Vec<PaintedBounds>,
264 /// The viewport the last layout pass ran for, or `None` before the first.
265 viewport: Option<Size>,
266 /// The scale the next layout pass measures at. A widget that sizes itself
267 /// from pixel data measures against it.
268 scale: f32,
269 /// How many times the layout engine has run for this tree.
270 layout_runs: u64,
271 /// The deepest node the pointer is over, or `None` when it is over no
272 /// node that can respond.
273 hovered: Option<WidgetId>,
274 /// The pointer position the last pointer event reported, in logical
275 /// pixels, so the hovered chain can be recomputed when the tree moves
276 /// under a pointer that stays still.
277 pointer: Point,
278 /// Whether the pointer is inside the window, so a layout pass only
279 /// recomputes hover from [`WidgetTree::pointer`] while it is.
280 pointer_inside: bool,
281 /// The node a primary press captured, while the button is held.
282 pressed: Option<WidgetId>,
283 /// Whether a press was released without its release, so a primary
284 /// release that arrives with no press held belongs to that press and
285 /// reaches no handler. The next primary press clears it.
286 released_early: bool,
287 /// The node that holds the keyboard focus, when one does.
288 focused: Option<WidgetId>,
289 /// The node the focus fell off of because it became disabled or hidden.
290 ///
291 /// The next key event still bubbles from that node, so a shortcut above a
292 /// disabled control keeps working. Any focus change consumes the anchor,
293 /// and so does any press that is not outside an active focus trap.
294 stale_focus: Option<WidgetId>,
295 /// Whether the focus last moved by keyboard navigation, so a focus ring
296 /// can draw for keyboard users alone. A Tab or an arrow key turns this on
297 /// and a pointer press turns it off.
298 keyboard_nav: bool,
299 /// The autofocus requests that mounted nodes queued and no layout pass
300 /// has resolved yet.
301 pending_autofocus: Vec<WidgetId>,
302 /// The mounted focus traps, oldest first.
303 ///
304 /// The last mounted trap wins, and removing a node ends every trap it
305 /// holds, which restores the one mounted before it and the focus held
306 /// when the oldest removed trap mounted.
307 focus_traps: Vec<FocusTrap>,
308 /// The nodes that carry a follow signal, so the follow resolution after
309 /// a layout pass visits them alone instead of every node. A removed node
310 /// leaves a stale id here until the next resolution drops it.
311 following: Vec<WidgetId>,
312 /// The nodes that pin a child, so the pin resolution after a layout
313 /// pass visits them alone. A removed node leaves a stale id here until
314 /// the next resolution drops it.
315 pinned: Vec<WidgetId>,
316 /// The theme that paint-time token resolution reads.
317 theme: Theme,
318 /// The nodes whose projected accessibility state may have changed, in the
319 /// order the changes arrived.
320 ///
321 /// Duplicates are allowed and removed when the changes are taken. When
322 /// the list outgrows [`A11Y_DIRTY_CAP`], it is cleared and
323 /// [`WidgetTree::a11y_reset`] is raised, which makes the next projection
324 /// rebuild from scratch instead.
325 a11y_dirty: Vec<WidgetId>,
326 /// Whether the next projection must be rebuilt from scratch.
327 a11y_reset: bool,
328}
329
330impl WidgetTree {
331 /// Creates an empty tree.
332 pub(crate) fn new() -> WidgetTree {
333 WidgetTree {
334 nodes: SlotMap::with_key(),
335 taffy: taffy::TaffyTree::new(),
336 root: None,
337 paint_queue: Vec::new(),
338 layout_dirty: false,
339 pending_damage: Vec::new(),
340 viewport: None,
341 scale: 1.0,
342 layout_runs: 0,
343 hovered: None,
344 pointer: Point::default(),
345 pointer_inside: false,
346 pressed: None,
347 released_early: false,
348 focused: None,
349 stale_focus: None,
350 keyboard_nav: false,
351 pending_autofocus: Vec::new(),
352 focus_traps: Vec::new(),
353 following: Vec::new(),
354 pinned: Vec::new(),
355 theme: Theme::light(),
356 a11y_dirty: Vec::new(),
357 a11y_reset: false,
358 }
359 }
360
361 /// Adds a node for `widget` under `parent`, or as the root when `parent` is
362 /// `None`, and returns the new node's id.
363 ///
364 /// The node starts [`Dirty::Layout`], because it has never been laid out.
365 ///
366 /// # Panics
367 ///
368 /// Panics when `parent` is not a live id, or when `parent` is `None` and
369 /// the tree already has a root. Both are internal invariants: callers pass
370 /// ids that this tree handed out.
371 pub(crate) fn insert(
372 &mut self,
373 parent: Option<WidgetId>,
374 widget: Box<dyn Widget>,
375 style: Style,
376 ) -> WidgetId {
377 match parent {
378 Some(parent) => assert!(
379 self.nodes.contains_key(parent),
380 "the parent id is a live node"
381 ),
382 None => assert!(
383 self.root.is_none(),
384 "a tree has one root, and this one already has a root node"
385 ),
386 }
387
388 let taffy = self
389 .taffy
390 .new_leaf(style.layout.to_taffy())
391 .expect("the layout engine accepts a new leaf node");
392 let owner = match parent {
393 Some(parent) => self
394 .nodes
395 .get(parent)
396 .expect("the parent id is a live node")
397 .owner
398 .child(),
399 None => Owner::new(),
400 };
401 let node = Node {
402 widget,
403 parent,
404 children: Vec::new(),
405 resolved: style.clone(),
406 base: style,
407 variants: None,
408 uses_tokens: false,
409 taffy,
410 owner,
411 dirty: Dirty::Clean,
412 rect: Rect::default(),
413 content: Rect::default(),
414 paint_shift: Point::default(),
415 paint_rect: Rect::default(),
416 paint_content: Rect::default(),
417 clip: Rect::default(),
418 scroll_offset: Point::default(),
419 follow: None,
420 pin: None,
421 interaction: Interaction::default(),
422 cursor: None,
423 handlers: Handlers::default(),
424 a11y_role: None,
425 a11y_label: None,
426 ops: Vec::new(),
427 painted: None,
428 };
429 let id = self.nodes.insert(node);
430 self.taffy
431 .set_node_context(taffy, Some(id))
432 .expect("the new layout node is live");
433
434 match parent {
435 Some(parent) => {
436 let parent_node = self.nodes.get_mut(parent).expect("the parent id is live");
437 parent_node.children.push(id);
438 let parent_taffy = parent_node.taffy;
439 self.taffy
440 .add_child(parent_taffy, taffy)
441 .expect("both layout nodes are live");
442 }
443 None => self.root = Some(id),
444 }
445
446 self.mark(id, Dirty::Layout);
447 if let Some(parent) = parent {
448 self.push_a11y(parent);
449 }
450 id
451 }
452
453 /// Removes the node `id` and every node below it.
454 ///
455 /// Each removed node cleans its reactive owner up, which stops its
456 /// bindings, and contributes its last painted bounds to the removed
457 /// damage. The node is detached from its parent, which is marked for
458 /// layout. A removed node loses the focus and the press capture. A stale
459 /// id does nothing.
460 pub(crate) fn remove(&mut self, id: WidgetId) {
461 let Some(node) = self.nodes.get(id) else {
462 return;
463 };
464 let parent = node.parent;
465 if self.root == Some(id) {
466 self.root = None;
467 }
468 if let Some(parent) = parent
469 && let Some(parent_node) = self.nodes.get_mut(parent)
470 {
471 parent_node.children.retain(|child| *child != id);
472 }
473
474 let mut order = Vec::new();
475 let mut stack = vec![id];
476 while let Some(current) = stack.pop() {
477 order.push(current);
478 if let Some(node) = self.nodes.get(current) {
479 stack.extend(node.children.iter().copied());
480 }
481 }
482 if self.focused.is_some_and(|focused| order.contains(&focused)) {
483 self.focused = None;
484 }
485 if self.stale_focus.is_some_and(|stale| order.contains(&stale)) {
486 self.stale_focus = None;
487 }
488 // A drag whose node is gone has nothing left to capture, and hover
489 // only follows the pointer again once no press is held.
490 if self.pressed.is_some_and(|pressed| order.contains(&pressed)) {
491 self.pressed = None;
492 }
493 // A removed node keeps neither its autofocus request nor its trap:
494 // the request is dropped, and the trap mounted before it, if any,
495 // takes over. The focus goes back to where it was when the oldest
496 // removed active trap became active, since every later one became
497 // active inside it. A hidden trap already gave its focus back.
498 self.pending_autofocus.retain(|id| !order.contains(id));
499 let restore = self
500 .focus_traps
501 .iter()
502 .find(|trap| trap.active && order.contains(&trap.node))
503 .and_then(|trap| trap.restore)
504 .filter(|restore| !order.contains(restore));
505 self.focus_traps.retain(|trap| !order.contains(&trap.node));
506
507 // Deepest node first, so every taffy node is still attached to a live
508 // parent when it is removed, and so an owner's bindings stop before
509 // the owners above it dispose the signals they read.
510 let mut removed_taffy = Vec::new();
511 for current in order.into_iter().rev() {
512 let Some(node) = self.nodes.get_mut(current) else {
513 continue;
514 };
515 let owner = node.owner.clone();
516 let painted = node.painted.take();
517 let taffy = node.taffy;
518 owner.cleanup();
519 if let Some(painted) = painted {
520 self.pending_damage.push(painted);
521 }
522 self.taffy
523 .remove(taffy)
524 .expect("every widget node has a live layout node");
525 self.nodes.remove(current);
526 removed_taffy.push(taffy);
527 }
528
529 if let Some(parent) = parent {
530 self.mark(parent, Dirty::Layout);
531 self.push_a11y(parent);
532 }
533 self.drop_stale_hoisted_children(parent, &removed_taffy);
534 if self.focused.is_none()
535 && let Some(restore) = restore
536 {
537 self.request_focus(restore);
538 }
539 }
540
541 /// Recreates the layout node of every ancestor, starting at `parent`,
542 /// whose hoisted child list still names one of `removed_taffy`.
543 ///
544 /// The layout engine records an absolutely positioned node in the hoisted
545 /// child list of its containing block, and clears that list only when the
546 /// block's own layout runs again. A block that lost its last child, or one
547 /// that is hidden, runs no layout of its own, so it keeps the stale id and
548 /// the next layout pass reads it and panics. A containing block is always
549 /// an ancestor, so only the removed node's ancestors need checking.
550 fn drop_stale_hoisted_children(
551 &mut self,
552 parent: Option<WidgetId>,
553 removed_taffy: &[taffy::NodeId],
554 ) {
555 let mut ancestor = parent;
556 while let Some(id) = ancestor {
557 let Some(node) = self.nodes.get(id) else {
558 break;
559 };
560 ancestor = node.parent;
561 let stale = self
562 .taffy
563 .hoisted_children(node.taffy)
564 .expect("every widget node has a live layout node")
565 .iter()
566 .any(|hoisted| removed_taffy.contains(hoisted));
567 if stale {
568 self.recreate_layout_node(id);
569 }
570 }
571 }
572
573 /// Replaces the layout node of `id` with a fresh one, which carries an
574 /// empty hoisted child list that the next layout pass rebuilds.
575 ///
576 /// The fresh node takes the old node's place among its parent's layout
577 /// children, because the engine only lays out nodes it reaches from the
578 /// root, and an unattached node would keep the default empty layout.
579 fn recreate_layout_node(&mut self, id: WidgetId) {
580 let node = self.nodes.get(id).expect("the node is live");
581 let style = node.resolved.layout.to_taffy();
582 let old = node.taffy;
583 let parent = self.taffy.parent(old);
584 let index = parent.and_then(|parent| {
585 self.taffy
586 .children(parent)
587 .expect("the parent is a live layout node")
588 .iter()
589 .position(|child| *child == old)
590 });
591 let children = self
592 .taffy
593 .children(old)
594 .expect("every widget node has a live layout node");
595 self.taffy
596 .remove(old)
597 .expect("every widget node has a live layout node");
598 let taffy = self
599 .taffy
600 .new_leaf(style)
601 .expect("the layout engine accepts a new leaf node");
602 self.taffy
603 .set_node_context(taffy, Some(id))
604 .expect("the new layout node is live");
605 for child in children {
606 self.taffy
607 .add_child(taffy, child)
608 .expect("the reattached children are live layout nodes");
609 }
610 if let Some(parent) = parent {
611 match index {
612 Some(index) => self
613 .taffy
614 .insert_child_at_index(parent, index, taffy)
615 .expect("the index fits the parent's child list"),
616 None => self
617 .taffy
618 .add_child(parent, taffy)
619 .expect("both layout nodes are live"),
620 }
621 }
622 self.nodes.get_mut(id).expect("the node is live").taffy = taffy;
623 self.mark(id, Dirty::Layout);
624 }
625
626 /// Reorders the children of `id` to `children` and marks the node for
627 /// layout.
628 ///
629 /// A reorder that changes the order also queues the painted bounds of the
630 /// whole subtree for a repaint, because the reorder restacks the pixels
631 /// the children share without changing any rectangle that would announce
632 /// it. The next frame repaints those bounds from the display list in the
633 /// new order, which puts the new stacking on the screen even when nothing
634 /// below `id` repaints on its own.
635 ///
636 /// # Panics
637 ///
638 /// Panics unless `children` is a permutation of the node's current children.
639 /// Callers reorder children the node already has.
640 pub(crate) fn set_children(&mut self, id: WidgetId, children: Vec<WidgetId>) {
641 let Some(node) = self.nodes.get(id) else {
642 return;
643 };
644 let taffy = node.taffy;
645 let restacked = children_differ_in_order(&node.children, &children);
646
647 let mut reordered_ids = children.clone();
648 reordered_ids.sort_unstable();
649 let mut current = node.children.clone();
650 current.sort_unstable();
651 assert_eq!(
652 reordered_ids, current,
653 "set_children reorders the children the node already has"
654 );
655 for child in &children {
656 assert_eq!(
657 self.nodes.get(*child).and_then(|node| node.parent),
658 Some(id),
659 "every new child is a child of the node"
660 );
661 }
662
663 let taffy_children: Vec<taffy::NodeId> = children
664 .iter()
665 .map(|child| self.nodes[*child].taffy)
666 .collect();
667 self.taffy
668 .set_children(taffy, &taffy_children)
669 .expect("the children are live layout nodes of this parent");
670 self.nodes[id].children = children;
671 if restacked {
672 self.queue_subtree_damage(id);
673 }
674 self.mark(id, Dirty::Layout);
675 self.push_a11y(id);
676 }
677
678 /// Adds the painted bounds of every node below `id`, `id` included, to the
679 /// pending damage.
680 ///
681 /// A child reorder restacks the pixels the reordered subtrees share
682 /// without moving or repainting any node, so no node is marked and no
683 /// rectangle changes. Repainting these bounds from the display list in the
684 /// new child order draws the new stacking.
685 fn queue_subtree_damage(&mut self, id: WidgetId) {
686 let mut stack = vec![id];
687 while let Some(current) = stack.pop() {
688 let Some(node) = self.nodes.get(current) else {
689 continue;
690 };
691 if let Some(painted) = node.painted {
692 self.pending_damage.push(painted);
693 }
694 stack.extend_from_slice(&node.children);
695 }
696 }
697
698 /// Raises the node's dirty flag to `dirty` and queues it for painting the
699 /// first time it leaves [`Dirty::Clean`].
700 ///
701 /// The node is queued for the accessibility projection too when the
702 /// projection could show the change: on a layout change, and on a
703 /// repaint of a node the projection shows.
704 ///
705 /// Marking the node for layout also invalidates the size the layout engine
706 /// cached for it and its ancestors, because a widget can change what it
707 /// measures without any change to its style.
708 ///
709 /// A stale id does nothing.
710 pub(crate) fn mark(&mut self, id: WidgetId, dirty: Dirty) {
711 let Some(node) = self.nodes.get_mut(id) else {
712 return;
713 };
714 if node.dirty >= dirty {
715 return;
716 }
717 let was_clean = node.dirty == Dirty::Clean;
718 let taffy = node.taffy;
719 node.dirty = dirty;
720 // A repaint changes what the projection shows only through the
721 // node's own semantics, so a node the projection leaves out, such as
722 // a blinking caret, queues nothing for it. A layout change can move
723 // the bounds of the projected nodes around it.
724 let projected = dirty == Dirty::Layout || Self::effective_role(node).is_some();
725 if was_clean {
726 self.paint_queue.push(id);
727 }
728 if projected && (was_clean || dirty == Dirty::Layout) {
729 self.push_a11y(id);
730 }
731 if dirty == Dirty::Layout {
732 self.layout_dirty = true;
733 self.taffy
734 .mark_dirty(taffy)
735 .expect("every widget node has a live layout node");
736 }
737 }
738
739 /// Returns the node's resolved style, with the active variant patches
740 /// applied, or `None` when the id is stale.
741 pub(crate) fn style(&self, id: WidgetId) -> Option<&Style> {
742 self.nodes.get(id).map(|node| &node.resolved)
743 }
744
745 /// Sets the style patches the node resolves its style from, and resolves
746 /// the style again.
747 ///
748 /// Called once, while the node is being mounted, before the interaction
749 /// bits are set. A stale id does nothing.
750 pub(crate) fn set_variants(&mut self, id: WidgetId, variants: Option<Rc<Variants>>) {
751 if let Some(node) = self.nodes.get_mut(id) {
752 node.variants = variants;
753 }
754 self.re_resolve(id);
755 }
756
757 /// Applies `apply` to the node's base layout properties, then resolves
758 /// the style again.
759 ///
760 /// The resolved style decides what the change invalidated: when its
761 /// layout properties differ from the ones the last resolution produced,
762 /// the layout engine's style is updated and the node marked for layout;
763 /// otherwise nothing happens when the resolved style did not change, or
764 /// the node is marked for paint when only its paint properties changed. A
765 /// patch can hold a base change back, and then no node is marked at all.
766 /// A stale id does nothing.
767 pub(crate) fn apply_layout(&mut self, id: WidgetId, apply: impl FnOnce(&mut LayoutProps)) {
768 let Some(node) = self.nodes.get_mut(id) else {
769 return;
770 };
771 let mut layout = node.base.layout.clone();
772 apply(&mut layout);
773 if layout == node.base.layout {
774 return;
775 }
776 node.base.layout = layout;
777 self.re_resolve(id);
778 }
779
780 /// Applies `apply` to the node's base paint properties, then resolves the
781 /// style again, as [`WidgetTree::apply_layout`] does for layout
782 /// properties. A stale id does nothing.
783 pub(crate) fn apply_paint(&mut self, id: WidgetId, apply: impl FnOnce(&mut PaintProps)) {
784 let Some(node) = self.nodes.get_mut(id) else {
785 return;
786 };
787 let mut paint = node.base.paint.clone();
788 apply(&mut paint);
789 if paint == node.base.paint {
790 return;
791 }
792 node.base.paint = paint;
793 self.re_resolve(id);
794 }
795
796 /// Recomputes the node's resolved style from its base style, its variant
797 /// patches and its interaction state, and marks the node for what the
798 /// change invalidated.
799 ///
800 /// A change to the resolved layout properties updates the layout engine's
801 /// style and marks the node for layout; a change to the paint properties
802 /// alone marks it for paint. An equal resolved style does nothing, so a
803 /// patch that rewrites a value the resolved style already has repaints
804 /// nothing. A node that the resolution hid loses the keyboard focus at
805 /// once, before the style can draw a focus ring on a hidden node. A stale
806 /// id does nothing.
807 pub(crate) fn re_resolve(&mut self, id: WidgetId) {
808 let Some(node) = self.nodes.get(id) else {
809 return;
810 };
811 let empty = Variants::default();
812 let variants = node.variants.as_deref().unwrap_or(&empty);
813 let resolved = resolve(&node.base, variants, node.interaction);
814 if resolved == node.resolved {
815 return;
816 }
817 let became_hidden = resolved.layout.hidden && !node.resolved.layout.hidden;
818 let became_visible = !resolved.layout.hidden && node.resolved.layout.hidden;
819 let layout_changed = resolved.layout != node.resolved.layout;
820 let taffy_style = resolved.layout.to_taffy();
821 let node = self.nodes.get_mut(id).expect("the node is live");
822 node.resolved = resolved;
823 if layout_changed {
824 let taffy = self.nodes[id].taffy;
825 self.taffy
826 .set_style(taffy, taffy_style)
827 .expect("every widget node has a live layout node");
828 self.mark(id, Dirty::Layout);
829 } else {
830 self.mark(id, Dirty::Paint);
831 }
832 if became_hidden {
833 self.drop_invalid_focus();
834 }
835 if (became_hidden || became_visible)
836 && let Some(parent) = self.nodes.get(id).and_then(|node| node.parent)
837 {
838 self.push_a11y(parent);
839 }
840 if became_visible {
841 self.push_a11y_visible_subtree(id);
842 }
843 }
844
845 /// Replaces the theme and marks every node whose last paint resolved a
846 /// value from the theme for repainting.
847 ///
848 /// Nodes whose paint consults no token paint the same pixels under any
849 /// theme, so they are left alone. An equal theme marks nothing. Which
850 /// nodes consulted the theme is what the paint pass recorded.
851 pub(crate) fn set_theme(&mut self, theme: Theme) {
852 if theme == self.theme {
853 return;
854 }
855 self.theme = theme;
856 let marked: Vec<WidgetId> = self
857 .nodes
858 .iter()
859 .filter(|(_, node)| node.uses_tokens)
860 .map(|(id, _)| id)
861 .collect();
862 for id in marked {
863 self.mark(id, Dirty::Paint);
864 }
865 }
866
867 /// Returns the node's widget as `W`, or `None` when the id is stale or the
868 /// widget has another type.
869 pub(crate) fn widget_mut<W: Widget>(&mut self, id: WidgetId) -> Option<&mut W> {
870 let node = self.nodes.get_mut(id)?;
871 (node.widget.as_mut() as &mut dyn Any).downcast_mut::<W>()
872 }
873
874 /// Returns the node's reactive owner, or `None` when the id is stale.
875 pub(crate) fn owner(&self, id: WidgetId) -> Option<&Owner> {
876 self.nodes.get(id).map(|node| &node.owner)
877 }
878
879 /// Visits every visible node in paint order, passing the node's depth, the
880 /// name of its widget's concrete type and its absolute rectangle.
881 ///
882 /// The root has depth zero. A hidden node and its subtree are skipped,
883 /// because they are laid out as empty and paint nothing. The tree itself
884 /// stays private: this is how a caller outside the crate reads its
885 /// structure.
886 pub(crate) fn visit_nodes(&self, visit: &mut dyn FnMut(usize, &'static str, Rect)) {
887 if let Some(root) = self.root {
888 self.visit_from(root, 0, visit);
889 }
890 }
891
892 /// Appends `id` and its subtree to the visit started by
893 /// [`WidgetTree::visit_nodes`].
894 fn visit_from(
895 &self,
896 id: WidgetId,
897 depth: usize,
898 visit: &mut dyn FnMut(usize, &'static str, Rect),
899 ) {
900 let Some(node) = self.nodes.get(id) else {
901 return;
902 };
903 if node.resolved.layout.hidden {
904 return;
905 }
906 visit(depth, node.widget.type_name(), node.rect);
907 for &child in &node.children {
908 self.visit_from(child, depth + 1, visit);
909 }
910 }
911
912 /// Returns whether any projected accessibility state may have changed
913 /// since the last [`WidgetTree::take_a11y_changes`].
914 pub(crate) fn has_a11y_changes(&self) -> bool {
915 self.a11y_reset || !self.a11y_dirty.is_empty()
916 }
917
918 /// Takes the projected nodes that changed since the last call.
919 ///
920 /// A change to an unprojected node changes the child list of its nearest
921 /// projected ancestor, so it is reported as that ancestor, or as
922 /// [`A11yChanges::roots_changed`] when no ancestor projects. Stale ids,
923 /// ids that became hidden, and duplicates drop out. When the change queue
924 /// overflowed since the last call, `reset` is set and the changed list is
925 /// empty.
926 pub(crate) fn take_a11y_changes(&mut self) -> A11yChanges {
927 let mut changes = A11yChanges {
928 reset: std::mem::take(&mut self.a11y_reset),
929 ..A11yChanges::default()
930 };
931 let dirty = std::mem::take(&mut self.a11y_dirty);
932 if changes.reset {
933 return changes;
934 }
935 let mut seen = HashSet::new();
936 for id in dirty {
937 if !seen.insert(id) || !self.a11y_id_is_visible(id) {
938 continue;
939 }
940 let projected = if self.nodes.get(id).and_then(Self::effective_role).is_some() {
941 Some(id)
942 } else {
943 self.nearest_projected_ancestor(id)
944 };
945 match projected {
946 Some(projected) if projected == id || seen.insert(projected) => {
947 changes.changed.push(projected);
948 }
949 Some(_) => {}
950 None => changes.roots_changed = true,
951 }
952 }
953 changes
954 }
955
956 /// Returns whether `id` is live and neither it nor an ancestor is hidden.
957 fn a11y_id_is_visible(&self, id: WidgetId) -> bool {
958 let mut next = Some(id);
959 while let Some(current) = next {
960 let Some(node) = self.nodes.get(current) else {
961 return false;
962 };
963 if node.resolved.layout.hidden {
964 return false;
965 }
966 next = node.parent;
967 }
968 true
969 }
970
971 /// Records that the projected state of `id` may have changed.
972 ///
973 /// Changes queue freely and are deduplicated when taken. A queue that
974 /// outgrows [`A11Y_DIRTY_CAP`] is dropped and the next projection
975 /// rebuilds from scratch, which is cheaper than replaying thousands of
976 /// one-node updates.
977 pub(crate) fn push_a11y(&mut self, id: WidgetId) {
978 if self.a11y_dirty.len() >= A11Y_DIRTY_CAP {
979 self.a11y_dirty.clear();
980 self.a11y_reset = true;
981 return;
982 }
983 self.a11y_dirty.push(id);
984 }
985
986 /// Records `id` and its visible subtree as changed.
987 ///
988 /// A node that becomes visible again reattaches in the projection, so
989 /// every descendant that is visible again has to be rebuilt too.
990 fn push_a11y_visible_subtree(&mut self, id: WidgetId) {
991 let mut stack = vec![id];
992 while let Some(current) = stack.pop() {
993 let Some(node) = self.nodes.get(current) else {
994 continue;
995 };
996 if node.resolved.layout.hidden {
997 continue;
998 }
999 let children = node.children.clone();
1000 self.push_a11y(current);
1001 stack.extend_from_slice(&children);
1002 }
1003 }
1004
1005 /// Sets the role the projection reports for `id`.
1006 ///
1007 /// Called once, while the node is being mounted. A stale id does nothing.
1008 pub(crate) fn set_a11y_role(&mut self, id: WidgetId, role: Option<Role>) {
1009 if let Some(node) = self.nodes.get_mut(id) {
1010 node.a11y_role = role;
1011 }
1012 }
1013
1014 /// Sets the accessible label of `id` and records the change.
1015 ///
1016 /// Called when the node mounts and again by the binding of an
1017 /// [`accessible_label`](crate::element::Element::accessible_label)
1018 /// builder value. A stale id does nothing.
1019 pub(crate) fn set_a11y_label(&mut self, id: WidgetId, label: Option<String>) {
1020 let Some(node) = self.nodes.get_mut(id) else {
1021 return;
1022 };
1023 if node.a11y_label == label {
1024 return;
1025 }
1026 node.a11y_label = label;
1027 self.push_a11y(id);
1028 }
1029
1030 /// Projects every visible node in paint order, for an accessibility
1031 /// backend.
1032 ///
1033 /// A node is projected when its element set a role or its widget reports
1034 /// one; the children of an unprojected node attach to the nearest
1035 /// projected ancestor. A hidden node and its subtree are skipped, like
1036 /// in [`WidgetTree::visit_nodes`]. The tree itself stays private: this is
1037 /// how a caller outside the crate reads the accessibility tree.
1038 pub(crate) fn semantics_nodes(&self) -> Vec<SemanticsNode> {
1039 let mut nodes = Vec::new();
1040 let mut index_of = HashMap::new();
1041 if let Some(root) = self.root {
1042 self.project_from(root, None, &mut nodes, &mut index_of);
1043 }
1044 nodes
1045 }
1046
1047 /// Returns the projected nodes that no projected ancestor holds, in paint
1048 /// order.
1049 pub(crate) fn semantics_roots(&self) -> Vec<WidgetId> {
1050 let mut roots = Vec::new();
1051 if let Some(root) = self.root
1052 && let Some(node) = self.nodes.get(root)
1053 && !node.resolved.layout.hidden
1054 {
1055 if Self::effective_role(node).is_some() {
1056 roots.push(root);
1057 } else {
1058 self.collect_projected_children(root, &mut roots);
1059 }
1060 }
1061 roots
1062 }
1063
1064 /// Projects one node with the children the projection gives it, or
1065 /// [`None`] when the id is stale, hidden above or at `id`, or reports no
1066 /// role.
1067 pub(crate) fn semantics_node(&self, id: WidgetId) -> Option<SemanticsNode> {
1068 if !self.a11y_id_is_visible(id) {
1069 return None;
1070 }
1071 let node = self.nodes.get(id)?;
1072 let role = Self::effective_role(node)?;
1073 let parent = self.nearest_projected_ancestor(id);
1074 let mut out = Self::project_node(id, node, role, parent);
1075 self.collect_projected_children(id, &mut out.children);
1076 Some(out)
1077 }
1078
1079 /// Returns the nearest ancestor of `id` that projects, or [`None`] when
1080 /// every ancestor is unprojected.
1081 fn nearest_projected_ancestor(&self, id: WidgetId) -> Option<WidgetId> {
1082 let mut next = self.nodes.get(id).and_then(|node| node.parent);
1083 while let Some(current) = next {
1084 let node = self.nodes.get(current)?;
1085 if !node.resolved.layout.hidden && Self::effective_role(node).is_some() {
1086 return Some(current);
1087 }
1088 next = node.parent;
1089 }
1090 None
1091 }
1092
1093 /// Walks `id`'s visible subtree in paint order and appends the projected
1094 /// nodes to `nodes`.
1095 fn project_from(
1096 &self,
1097 id: WidgetId,
1098 projected_parent: Option<WidgetId>,
1099 nodes: &mut Vec<SemanticsNode>,
1100 index_of: &mut HashMap<WidgetId, usize>,
1101 ) {
1102 let Some(node) = self.nodes.get(id) else {
1103 return;
1104 };
1105 if node.resolved.layout.hidden {
1106 return;
1107 }
1108 let Some(role) = Self::effective_role(node) else {
1109 for &child in &node.children {
1110 self.project_from(child, projected_parent, nodes, index_of);
1111 }
1112 return;
1113 };
1114 let index = nodes.len();
1115 nodes.push(Self::project_node(id, node, role, projected_parent));
1116 index_of.insert(id, index);
1117 if let Some(parent) = projected_parent
1118 && let Some(&parent_index) = index_of.get(&parent)
1119 {
1120 nodes[parent_index].children.push(id);
1121 }
1122 for &child in &node.children {
1123 self.project_from(child, Some(id), nodes, index_of);
1124 }
1125 }
1126
1127 /// Builds the projected form of one node.
1128 fn project_node(
1129 id: WidgetId,
1130 node: &Node,
1131 role: Role,
1132 parent: Option<WidgetId>,
1133 ) -> SemanticsNode {
1134 // The widget builds its description, value included, once per
1135 // projection, since a text widget copies its whole text into it.
1136 let widget: &dyn Widget = node.widget.as_ref();
1137 let semantics = widget.semantics();
1138 let (label, value) = match semantics {
1139 Some(semantics) => (semantics.label, semantics.value),
1140 None => (None, None),
1141 };
1142 SemanticsNode {
1143 id,
1144 parent,
1145 children: Vec::new(),
1146 role,
1147 label: node.a11y_label.clone().or(label),
1148 value,
1149 bounds: node.rect,
1150 focusable: node.interaction.has(Interaction::FOCUSABLE),
1151 disabled: node.interaction.has(Interaction::DISABLED),
1152 focused: node.interaction.has(Interaction::FOCUSED),
1153 pressable: node.handlers.on_press.is_some(),
1154 }
1155 }
1156
1157 /// Appends the ids of `id`'s projected descendants to `out`, walking
1158 /// through unprojected children.
1159 fn collect_projected_children(&self, id: WidgetId, out: &mut Vec<WidgetId>) {
1160 let Some(node) = self.nodes.get(id) else {
1161 return;
1162 };
1163 if node.resolved.layout.hidden {
1164 return;
1165 }
1166 for &child in &node.children {
1167 let Some(child_node) = self.nodes.get(child) else {
1168 continue;
1169 };
1170 if child_node.resolved.layout.hidden {
1171 continue;
1172 }
1173 if Self::effective_role(child_node).is_some() {
1174 out.push(child);
1175 } else {
1176 self.collect_projected_children(child, out);
1177 }
1178 }
1179 }
1180
1181 /// Returns the role the projection reports for the node: the one its
1182 /// element set, else the one its widget reports.
1183 fn effective_role(node: &Node) -> Option<Role> {
1184 node.a11y_role.or_else(|| {
1185 let widget: &dyn Widget = node.widget.as_ref();
1186 widget.semantics().map(|semantics| semantics.role)
1187 })
1188 }
1189
1190 /// Panics unless the layout engine's tree and the node store describe the
1191 /// same parents, children, contexts and layout styles.
1192 #[cfg(test)]
1193 pub(crate) fn debug_assert_consistent(&self) {
1194 assert_eq!(
1195 self.taffy.total_node_count(),
1196 self.nodes.len(),
1197 "every widget node has exactly one layout node"
1198 );
1199 for (id, node) in &self.nodes {
1200 let taffy_children = self
1201 .taffy
1202 .children(node.taffy)
1203 .expect("every widget node has a live layout node");
1204 assert_eq!(
1205 taffy_children.len(),
1206 node.children.len(),
1207 "the child count agrees"
1208 );
1209 for (child, taffy_child) in node.children.iter().zip(taffy_children) {
1210 let child_node = &self.nodes[*child];
1211 assert_eq!(child_node.taffy, taffy_child, "the child order agrees");
1212 assert_eq!(
1213 child_node.parent,
1214 Some(id),
1215 "every child points back at its parent"
1216 );
1217 }
1218 assert_eq!(
1219 self.taffy.parent(node.taffy),
1220 node.parent.map(|parent| self.nodes[parent].taffy),
1221 "the parent agrees"
1222 );
1223 assert_eq!(
1224 self.taffy.get_node_context(node.taffy),
1225 Some(&id),
1226 "the layout node's context is the widget id"
1227 );
1228 assert_eq!(
1229 self.taffy
1230 .style(node.taffy)
1231 .expect("every widget node has a live layout node"),
1232 &node.resolved.layout.to_taffy(),
1233 "the layout style agrees"
1234 );
1235 }
1236 for trap in &self.focus_traps {
1237 assert!(
1238 self.nodes.contains_key(trap.node),
1239 "every mounted focus trap is a live node"
1240 );
1241 }
1242 for id in &self.pending_autofocus {
1243 assert!(
1244 self.nodes.contains_key(*id),
1245 "every queued autofocus request belongs to a live node"
1246 );
1247 }
1248 }
1249}
1250
1251/// The state that tests outside this module observe without reading the store.
1252#[cfg(test)]
1253impl WidgetTree {
1254 /// Returns the node's dirty flag, or `None` when the id is stale.
1255 pub(crate) fn dirty(&self, id: WidgetId) -> Option<Dirty> {
1256 self.nodes.get(id).map(|node| node.dirty)
1257 }
1258
1259 /// Returns whether any node needs a new layout pass.
1260 pub(crate) fn layout_is_dirty(&self) -> bool {
1261 self.layout_dirty
1262 }
1263
1264 /// Returns the node's absolute rectangle, or `None` when the id is stale.
1265 pub(crate) fn rect(&self, id: WidgetId) -> Option<Rect> {
1266 self.nodes.get(id).map(|node| node.rect)
1267 }
1268
1269 /// Returns the rectangle the node is clipped to, or `None` when the id is
1270 /// stale.
1271 pub(crate) fn clip(&self, id: WidgetId) -> Option<Rect> {
1272 self.nodes.get(id).map(|node| node.clip)
1273 }
1274
1275 /// Returns how many times the layout engine has run for this tree.
1276 pub(crate) fn layout_runs(&self) -> u64 {
1277 self.layout_runs
1278 }
1279
1280 /// Returns the node's children in paint order, or `None` when the id is
1281 /// stale.
1282 pub(crate) fn children(&self, id: WidgetId) -> Option<&[WidgetId]> {
1283 self.nodes.get(id).map(|node| node.children.as_slice())
1284 }
1285
1286 /// Returns the number of nodes in the tree.
1287 pub(crate) fn node_count(&self) -> usize {
1288 self.nodes.len()
1289 }
1290
1291 /// Clears the dirty state of the whole tree, as a frame does once it has
1292 /// laid the tree out and painted every queued node.
1293 pub(crate) fn clear_dirty(&mut self) {
1294 for node in self.nodes.values_mut() {
1295 node.dirty = Dirty::Clean;
1296 }
1297 self.paint_queue.clear();
1298 self.layout_dirty = false;
1299 self.pending_damage.clear();
1300 }
1301}
1302
1303#[cfg(test)]
1304#[path = "tree_tests.rs"]
1305mod tests;