Skip to main content

tessera_ui/
component_tree.rs

1mod constraint;
2mod node;
3mod node_map;
4mod pointer;
5mod replay;
6mod scene;
7
8use std::{
9    cell::Cell,
10    collections::{BinaryHeap, VecDeque},
11    num::NonZero,
12    sync::Arc,
13};
14
15use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
16use smallvec::SmallVec;
17use tracing::{debug, warn};
18
19use crate::{
20    NodeId, PxRect,
21    clipboard::{self, ClipboardInput, ClipboardResponse, QueuedClipboardAction},
22    cursor::{CursorEventContent, MOUSE_POINTER_ID, PointerChange},
23    focus::{
24        FocusDirection, FocusHandleId, FocusOwner, PendingFocusCallbackInvocation, bind_focus_owner,
25    },
26    keyboard_state::QueuedInput,
27    layout::{LayoutResult, RenderInput},
28    modifier::{
29        DrawModifierContent, DrawModifierContext, OrderedModifierAction, PointerInputModifierNode,
30    },
31    px::{PxPosition, PxSize},
32    render_graph::RenderGraph,
33    runtime::{
34        LayoutDirtyNodes, RuntimePhase, StructureReconcileResult, TesseraRuntime,
35        push_current_component_instance_key, push_current_node_with_instance_logic_id, push_phase,
36    },
37    time::Instant,
38};
39
40pub use constraint::{AxisConstraint, Constraint, ParentConstraint};
41pub use node::{
42    ComputedData, GloballyPositionedHandler, ImeInput, ImeInputHandlerFn, ImeRequest, ImeSession,
43    KeyboardInput, KeyboardInputHandlerFn, MeasurementError, PointerEventPass, PointerInput,
44    PointerInputHandlerFn,
45};
46
47pub(crate) use node::{
48    ComponentNode, ComponentNodeMetaData, ComponentNodeMetaDatas, ComponentNodeTree, NodeRole,
49    WindowDragRequest, WindowRequests, direct_layout_children, measure_node, sibling_index,
50};
51pub(crate) use scene::SceneRope;
52
53#[cfg(feature = "profiling")]
54use crate::profiler::{NodeMeta, Phase as ProfilerPhase, ScopeGuard as ProfilerScopeGuard};
55
56#[derive(Clone)]
57pub(crate) struct LayoutSnapshotEntry {
58    pub constraint_key: Constraint,
59    pub consumed_insets: crate::Insets,
60    children: node::CachedLayoutChildren,
61    pub layout_result: LayoutResult,
62    pub base_placements: Option<Arc<[(u64, PxPosition)]>>,
63    pub intrinsic_dependencies: bool,
64    pub draw_layout: NodeDrawLayout,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq)]
68pub(crate) struct DrawLayoutBounds {
69    pub position: PxPosition,
70    pub size: ComputedData,
71}
72
73#[derive(Clone, PartialEq)]
74pub(crate) struct NodeDrawLayout {
75    // Cached frames share immutable layer bounds instead of allocating a copy
76    // for every restored node.
77    pub layers: Option<Arc<[Option<DrawLayoutBounds>]>>,
78    pub base_constraint: Constraint,
79    pub base_size: ComputedData,
80}
81
82impl Default for NodeDrawLayout {
83    fn default() -> Self {
84        Self {
85            layers: None,
86            base_constraint: Constraint::NONE,
87            base_size: ComputedData::ZERO,
88        }
89    }
90}
91
92#[derive(Default)]
93pub(crate) struct LayoutSnapshotMap {
94    entries: HashMap<u64, Arc<LayoutSnapshotEntry>>,
95    deferred: HashMap<u64, DeferredLayoutInvalidation>,
96}
97
98#[derive(Clone, Default)]
99pub(crate) struct DeferredLayoutInvalidation {
100    pub measure: bool,
101    pub placement: bool,
102    pub children: HashSet<u64>,
103}
104
105impl LayoutSnapshotMap {
106    pub(crate) fn clear(&mut self) {
107        self.entries.clear();
108        self.deferred.clear();
109    }
110
111    pub(crate) fn remove(&mut self, instance_key: &u64) -> Option<Arc<LayoutSnapshotEntry>> {
112        self.deferred.remove(instance_key);
113        self.entries.remove(instance_key)
114    }
115
116    pub(crate) fn insert(
117        &mut self,
118        instance_key: u64,
119        entry: LayoutSnapshotEntry,
120    ) -> Option<Arc<LayoutSnapshotEntry>> {
121        self.deferred.remove(&instance_key);
122        self.entries.insert(instance_key, Arc::new(entry))
123    }
124
125    pub(crate) fn get_cloned(&self, instance_key: &u64) -> Option<Arc<LayoutSnapshotEntry>> {
126        self.entries.get(instance_key).cloned()
127    }
128}
129
130pub(crate) fn nearest_replay_boundary_instance_key(
131    node_id: NodeId,
132    tree: &ComponentNodeTree,
133) -> u64 {
134    let mut current_id = node_id;
135    let mut fallback = 0;
136
137    loop {
138        let Some(node_ref) = tree.get(current_id) else {
139            return fallback;
140        };
141        let node = node_ref.get();
142        if fallback == 0 {
143            fallback = node.instance_key;
144        }
145        if node.replay_boundary {
146            return node.instance_key;
147        }
148        let Some(parent_id) = node_ref.parent() else {
149            return fallback;
150        };
151        current_id = parent_id;
152    }
153}
154
155pub(crate) fn clear_layout_snapshots() {
156    TesseraRuntime::with_mut(|runtime| {
157        runtime.component_tree.clear_layout_snapshots();
158    });
159}
160
161/// Invokes globally positioned callbacks collected by the last compute pass.
162///
163/// Must be called after the runtime borrow used by `compute` is released so
164/// callbacks can freely update state.
165pub(crate) fn flush_pending_globally_positioned_callbacks() {
166    let invocations = TesseraRuntime::with_mut(|runtime| {
167        runtime
168            .component_tree
169            .take_pending_globally_positioned_invocations()
170    });
171    for (handler, rect) in invocations {
172        handler.call(rect);
173    }
174    TesseraRuntime::with_mut(|runtime| runtime.component_tree.focus_owner_mut().finish_layout());
175}
176
177#[derive(Clone, Copy)]
178pub(crate) struct LayoutContext<'a> {
179    pub consumed_insets: &'a Cell<crate::Insets>,
180    pub state_reader_instance_key: Option<u64>,
181    snapshots: *mut LayoutSnapshotMap,
182    dirty_paths: Option<&'a HashMap<NodeId, DirtyLayoutPath>>,
183    diagnostics: *mut LayoutDiagnosticsCollector,
184    pub intrinsic: bool,
185}
186
187struct DirtyLayoutPath {
188    measure: bool,
189    placement: bool,
190    children: SmallVec<[NodeId; 1]>,
191}
192
193#[cfg_attr(not(feature = "profiling"), allow(dead_code))]
194#[derive(Clone, Copy, Debug, Default)]
195/// Layout work observed during one frame.
196///
197/// Work counts include every settling pass; only pointer dispatch nodes are distinct.
198pub struct LayoutFrameDiagnostics {
199    /// Number of nodes whose window geometry was resolved in this frame.
200    pub geometry_nodes_updated: u64,
201    /// Geometry traversal entries, including entries that immediately reused a
202    /// cache.
203    pub geometry_nodes_visited: u64,
204    /// Dirty direct children checked for cached size dependencies.
205    pub measure_dirty_children_checked: u64,
206    /// Nodes examined while refreshing pointer geometry and dispatch plans.
207    pub pointer_nodes_updated: u64,
208    /// Distinct pointer nodes selected for dispatch in this frame.
209    pub pointer_nodes_dispatched: u64,
210    /// Spatial candidates checked for exact pointer hits.
211    pub pointer_hit_test_candidates: u64,
212    /// Nodes whose draw commands were recorded.
213    pub paint_nodes_recorded: u64,
214    /// Persistent scene branches updated, including affected ancestors.
215    pub scene_nodes_updated: u64,
216    /// Nodes examined while updating the semantic index.
217    pub accessibility_nodes_updated: u64,
218    /// Nodes with changed measurement or child-placement inputs.
219    pub dirty_nodes_param: u64,
220    /// Nodes whose direct layout children changed.
221    pub dirty_nodes_structural: u64,
222    /// Invalidated layout nodes and the ancestors that measure them.
223    pub dirty_nodes_with_ancestors: u64,
224    /// Time spent identifying affected ancestors, in nanoseconds.
225    pub dirty_expand_ns: u64,
226    /// Measurement entrypoint calls, including cache lookups.
227    pub measure_node_calls: u64,
228    /// Cache hits that skipped an unchanged subtree.
229    pub cache_hits_direct: u64,
230    /// Cache hits after checking changed descendants or updating placements.
231    pub cache_hits_boundary: u64,
232    /// Cache misses without a reusable size snapshot.
233    pub cache_miss_no_entry: u64,
234    /// Cache misses caused by changed parent constraints.
235    pub cache_miss_constraint: u64,
236    /// Cache misses caused by the node's own layout inputs.
237    pub cache_miss_dirty_self: u64,
238    /// Cache misses caused by child size dependencies.
239    pub cache_miss_child_size: u64,
240    /// Measured or repositioned layouts saved for reuse.
241    pub cache_store_count: u64,
242    /// Layouts excluded from caching because child measurements were incomplete
243    /// or inconsistent.
244    pub cache_drop_non_cacheable_count: u64,
245}
246
247impl LayoutFrameDiagnostics {
248    pub(crate) fn accumulate_pass(&mut self, pass: Self) {
249        self.geometry_nodes_updated += pass.geometry_nodes_updated;
250        self.geometry_nodes_visited += pass.geometry_nodes_visited;
251        self.measure_dirty_children_checked += pass.measure_dirty_children_checked;
252        self.pointer_nodes_updated += pass.pointer_nodes_updated;
253        // Pointer selection is already deduplicated across the frame.
254        self.pointer_nodes_dispatched = pass.pointer_nodes_dispatched;
255        self.pointer_hit_test_candidates += pass.pointer_hit_test_candidates;
256        self.paint_nodes_recorded += pass.paint_nodes_recorded;
257        self.scene_nodes_updated += pass.scene_nodes_updated;
258        self.accessibility_nodes_updated += pass.accessibility_nodes_updated;
259        self.dirty_nodes_param += pass.dirty_nodes_param;
260        self.dirty_nodes_structural += pass.dirty_nodes_structural;
261        self.dirty_nodes_with_ancestors += pass.dirty_nodes_with_ancestors;
262        self.dirty_expand_ns += pass.dirty_expand_ns;
263        self.measure_node_calls += pass.measure_node_calls;
264        self.cache_hits_direct += pass.cache_hits_direct;
265        self.cache_hits_boundary += pass.cache_hits_boundary;
266        self.cache_miss_no_entry += pass.cache_miss_no_entry;
267        self.cache_miss_constraint += pass.cache_miss_constraint;
268        self.cache_miss_dirty_self += pass.cache_miss_dirty_self;
269        self.cache_miss_child_size += pass.cache_miss_child_size;
270        self.cache_store_count += pass.cache_store_count;
271        self.cache_drop_non_cacheable_count += pass.cache_drop_non_cacheable_count;
272    }
273}
274
275#[derive(Default)]
276pub(crate) struct LayoutDiagnosticsCollector {
277    measure_dirty_children_checked: u64,
278    measure_node_calls: u64,
279    cache_hits_direct: u64,
280    cache_hits_boundary: u64,
281    cache_miss_no_entry: u64,
282    cache_miss_constraint: u64,
283    cache_miss_dirty_self: u64,
284    cache_miss_child_size: u64,
285    cache_store_count: u64,
286    cache_drop_non_cacheable_count: u64,
287}
288
289impl LayoutDiagnosticsCollector {
290    fn snapshot(
291        &self,
292        dirty_nodes_param: u64,
293        dirty_nodes_structural: u64,
294        dirty_nodes_with_ancestors: u64,
295        dirty_expand_ns: u64,
296    ) -> LayoutFrameDiagnostics {
297        let cache_hits_direct = self.cache_hits_direct;
298        let cache_hits_boundary = self.cache_hits_boundary;
299        LayoutFrameDiagnostics {
300            geometry_nodes_updated: 0,
301            geometry_nodes_visited: 0,
302            measure_dirty_children_checked: self.measure_dirty_children_checked,
303            pointer_nodes_updated: 0,
304            pointer_nodes_dispatched: 0,
305            pointer_hit_test_candidates: 0,
306            paint_nodes_recorded: 0,
307            scene_nodes_updated: 0,
308            accessibility_nodes_updated: 0,
309            dirty_nodes_param,
310            dirty_nodes_structural,
311            dirty_nodes_with_ancestors,
312            dirty_expand_ns,
313            measure_node_calls: self.measure_node_calls,
314            cache_hits_direct,
315            cache_hits_boundary,
316            cache_miss_no_entry: self.cache_miss_no_entry,
317            cache_miss_constraint: self.cache_miss_constraint,
318            cache_miss_dirty_self: self.cache_miss_dirty_self,
319            cache_miss_child_size: self.cache_miss_child_size,
320            cache_store_count: self.cache_store_count,
321            cache_drop_non_cacheable_count: self.cache_drop_non_cacheable_count,
322        }
323    }
324}
325
326impl LayoutContext<'_> {
327    pub(crate) fn with_consumed_insets<R>(
328        &self,
329        insets: crate::Insets,
330        measure: impl FnOnce() -> R,
331    ) -> R {
332        struct Restore<'a>(&'a Cell<crate::Insets>, crate::Insets);
333        impl Drop for Restore<'_> {
334            fn drop(&mut self) {
335                self.0.set(self.1);
336            }
337        }
338        let _restore = Restore(self.consumed_insets, self.consumed_insets.replace(insets));
339        measure()
340    }
341
342    fn new<'a>(
343        consumed_insets: &'a Cell<crate::Insets>,
344        snapshots: &'a mut LayoutSnapshotMap,
345        dirty_paths: &'a HashMap<NodeId, DirtyLayoutPath>,
346        diagnostics: &'a mut LayoutDiagnosticsCollector,
347    ) -> LayoutContext<'a> {
348        LayoutContext {
349            consumed_insets,
350            state_reader_instance_key: None,
351            snapshots,
352            dirty_paths: Some(dirty_paths),
353            diagnostics,
354            intrinsic: false,
355        }
356    }
357
358    pub(crate) fn intrinsic_probe<'a>(
359        consumed_insets: &'a Cell<crate::Insets>,
360        snapshots: &'a mut LayoutSnapshotMap,
361        diagnostics: &'a mut LayoutDiagnosticsCollector,
362        parent: Option<&'a LayoutContext<'_>>,
363    ) -> LayoutContext<'a> {
364        LayoutContext {
365            consumed_insets,
366            state_reader_instance_key: crate::runtime::current_measured_node_instance_key(),
367            snapshots,
368            dirty_paths: None,
369            diagnostics: parent.map_or(diagnostics, |parent| parent.diagnostics),
370            intrinsic: true,
371        }
372    }
373
374    #[inline]
375    pub(crate) fn snapshot(&self, instance_key: u64) -> Option<Arc<LayoutSnapshotEntry>> {
376        // SAFETY: Layout snapshots are owned by the single-threaded compute
377        // pass. The pointer is created from a unique mutable borrow for the
378        // duration of the pass and only accessed on the same thread.
379        unsafe { (*self.snapshots).get_cloned(&instance_key) }
380    }
381
382    #[inline]
383    pub(crate) fn deferred_invalidation(
384        &self,
385        instance_key: u64,
386    ) -> Option<DeferredLayoutInvalidation> {
387        // SAFETY: See `snapshot`.
388        unsafe { (*self.snapshots).deferred.get(&instance_key).cloned() }
389    }
390
391    #[inline]
392    pub(crate) fn acknowledge_deferred_invalidation(&self, instance_key: u64) {
393        // SAFETY: See `snapshot`.
394        unsafe {
395            (*self.snapshots).deferred.remove(&instance_key);
396        }
397    }
398
399    #[inline]
400    pub(crate) fn insert_snapshot(&self, instance_key: u64, entry: LayoutSnapshotEntry) {
401        // SAFETY: See `snapshot`.
402        unsafe {
403            (*self.snapshots).insert(instance_key, entry);
404        }
405    }
406
407    #[inline]
408    pub(crate) fn remove_snapshot(&self, instance_key: u64) {
409        // SAFETY: See `snapshot`.
410        unsafe {
411            (*self.snapshots).remove(&instance_key);
412        }
413    }
414
415    #[inline]
416    pub(crate) fn inc_measure_node_calls(&self) {
417        // SAFETY: Layout diagnostics are owned by the single-threaded compute
418        // pass. The pointer is created from a unique mutable borrow for
419        // the duration of that pass and is only accessed on the same
420        // thread during recursive layout measurement.
421        unsafe {
422            (*self.diagnostics).measure_node_calls += 1;
423        }
424    }
425
426    pub(crate) fn inc_dirty_child_checked(&self) {
427        // SAFETY: See `inc_measure_node_calls`.
428        unsafe {
429            (*self.diagnostics).measure_dirty_children_checked += 1;
430        }
431    }
432
433    #[inline]
434    pub(crate) fn inc_cache_hit_direct(&self) {
435        unsafe {
436            (*self.diagnostics).cache_hits_direct += 1;
437        }
438    }
439
440    #[inline]
441    pub(crate) fn inc_cache_hit_boundary(&self) {
442        unsafe {
443            (*self.diagnostics).cache_hits_boundary += 1;
444        }
445    }
446
447    #[inline]
448    pub(crate) fn inc_cache_miss_no_entry(&self) {
449        unsafe {
450            (*self.diagnostics).cache_miss_no_entry += 1;
451        }
452    }
453
454    #[inline]
455    pub(crate) fn inc_cache_miss_constraint(&self) {
456        unsafe {
457            (*self.diagnostics).cache_miss_constraint += 1;
458        }
459    }
460
461    #[inline]
462    pub(crate) fn inc_cache_miss_dirty_self(&self) {
463        unsafe {
464            (*self.diagnostics).cache_miss_dirty_self += 1;
465        }
466    }
467
468    #[inline]
469    pub(crate) fn inc_cache_miss_child_size(&self) {
470        unsafe {
471            (*self.diagnostics).cache_miss_child_size += 1;
472        }
473    }
474
475    #[inline]
476    pub(crate) fn inc_cache_store_count(&self) {
477        unsafe {
478            (*self.diagnostics).cache_store_count += 1;
479        }
480    }
481
482    #[inline]
483    pub(crate) fn inc_cache_drop_non_cacheable_count(&self) {
484        unsafe {
485            (*self.diagnostics).cache_drop_non_cacheable_count += 1;
486        }
487    }
488}
489
490/// Parameters for the compute function
491pub(crate) struct ComputeParams {
492    pub screen_size: PxSize,
493    pub cursor_position: Option<PxPosition>,
494    pub pointer_changes: Vec<PointerChange>,
495    pub keyboard_events: Vec<QueuedInput>,
496    pub ime_events: Vec<QueuedInput<crate::ime_state::ImeEvent>>,
497    pub retry_focus_move: Option<FocusDirection>,
498    pub retry_focus_reveal: bool,
499    pub modifiers: winit::keyboard::ModifiersState,
500    pub layout_dirty_nodes: LayoutDirtyNodes,
501}
502
503#[derive(Debug)]
504pub(crate) enum ComputeMode {
505    Full,
506    #[cfg(any(test, feature = "testing"))]
507    LayoutOnly,
508}
509
510struct GlobalPositionSnapshot {
511    geometry: node::GeometryObservation,
512    handlers: SmallVec<[GloballyPositionedHandler; 2]>,
513}
514
515enum QueuedFocusedInput {
516    Keyboard(QueuedInput),
517    Clipboard(QueuedClipboardAction),
518    Ime(QueuedInput<crate::ime_state::ImeEvent>),
519}
520
521impl QueuedFocusedInput {
522    fn sequence(&self) -> u64 {
523        match self {
524            Self::Keyboard(event) => event.sequence,
525            Self::Clipboard(event) => event.sequence,
526            Self::Ime(event) => event.sequence,
527        }
528    }
529}
530
531/// Respents a component tree
532pub struct ComponentTree {
533    /// We use indextree as the tree structure
534    tree: indextree::Arena<ComponentNode>,
535    /// Components' metadatas
536    metadatas: ComponentNodeMetaDatas,
537    layout_snapshots: LayoutSnapshotMap,
538    frame_scene: scene::FrameScene,
539    pointer_index: pointer::PointerIndex,
540    accessibility_index: crate::accessibility::AccessibilityIndex,
541    #[cfg(feature = "profiling")]
542    profiler_nodes_cache: HashMap<NodeId, NodeMeta>,
543    #[cfg(feature = "profiling")]
544    profiler_dirty_nodes: HashSet<NodeId>,
545    scale_factor_bits: u64,
546    /// Used to remember the current node
547    node_queue: Vec<indextree::NodeId>,
548    replay_range: Option<ReplayRange>,
549    replay_insertions: ReplayInsertions,
550    replay_ancestry: replay::ReplayAncestry,
551    replay_changed_parents: HashSet<NodeId>,
552    node_ids_by_instance_key: HashMap<u64, indextree::NodeId>,
553    changed_frame_nodes: HashSet<NodeId>,
554    removed_frame_nodes: HashSet<NodeId>,
555    previous_structure_change_counts: [usize; 2],
556    removed_focus_owners: HashSet<u64>,
557    /// Active pointer hit paths keyed by pointer id.
558    /// Each path stores node instance keys from root to leaf.
559    active_pointer_paths: HashMap<u64, Vec<u64>>,
560    active_pointer_positions: HashMap<u64, Option<PxPosition>>,
561    pointer_work: (u64, HashSet<u64>),
562    /// Per-tree focus owner used for keyboard and IME routing.
563    focus_owner: FocusOwner,
564    pending_focused_input: VecDeque<QueuedFocusedInput>,
565    /// Last reported window-space bounds for nodes with globally positioned
566    /// callbacks, keyed by instance key.
567    last_global_rects: HashMap<u64, GlobalPositionSnapshot>,
568    observer_viewport: Option<PxSize>,
569    position_observer_nodes: HashSet<NodeId>,
570    window_observer_nodes: HashSet<NodeId>,
571    pending_position_observers: HashSet<NodeId>,
572    observer_spaces: HashMap<NodeId, u64>,
573    space_observers: HashMap<u64, HashSet<NodeId>>,
574    /// Globally positioned callbacks collected during compute and invoked
575    /// after the runtime borrow is released.
576    pending_globally_positioned_invocations:
577        Vec<(GloballyPositionedHandler, node::GeometryObservation)>,
578}
579
580#[derive(Clone, PartialEq)]
581pub(crate) enum ReplayReplaceError {
582    NodeNotFound,
583    RootNodeNotReplaceable,
584    ReplayDidNotCreateRoot,
585}
586
587#[derive(Default)]
588pub(crate) struct ReplayReplaceResult {
589    pub removed_instance_keys: HashSet<u64>,
590    pub removed_instance_logic_ids: HashSet<u64>,
591    insertions: ReplayInsertions,
592}
593
594impl ReplayReplaceResult {
595    fn clear(&mut self) {
596        clear_replay_set(&mut self.removed_instance_keys);
597        clear_replay_set(&mut self.removed_instance_logic_ids);
598        self.insertions.clear();
599    }
600
601    pub(crate) fn executed_keys(&self) -> impl Iterator<Item = u64> + '_ {
602        self.insertions
603            .rebuilt
604            .iter()
605            .filter_map(|&(key, _)| (!self.insertions.retained.keys.contains(&key)).then_some(key))
606    }
607
608    pub(crate) fn recomposed_logic_ids(&self) -> impl Iterator<Item = u64> + '_ {
609        self.insertions.rebuilt.iter().filter_map(|&(_, logic_id)| {
610            (!self.insertions.retained.logic_ids.contains(&logic_id)).then_some(logic_id)
611        })
612    }
613
614    pub(crate) fn retained_count(&self) -> usize {
615        self.insertions.retained.keys.len()
616    }
617
618    pub(crate) fn inserted_count(&self) -> usize {
619        self.insertions.rebuilt.len() + self.retained_count()
620    }
621}
622
623// A large replacement should not keep large scratch buffers for later small replays.
624const MAX_REPLAY_SCRATCH_CAPACITY: usize = 256;
625const INLINE_REPLAY_INSERTIONS: usize = 8;
626
627fn clear_replay_set(set: &mut HashSet<u64>) {
628    if set.capacity() > MAX_REPLAY_SCRATCH_CAPACITY {
629        *set = HashSet::default();
630    } else {
631        set.clear();
632    }
633}
634
635#[derive(Default)]
636struct ReplayIdentities {
637    keys: HashSet<u64>,
638    logic_ids: HashSet<u64>,
639}
640
641impl ReplayIdentities {
642    fn insert(&mut self, key: u64, logic_id: u64) {
643        self.keys.insert(key);
644        self.logic_ids.insert(logic_id);
645    }
646}
647
648#[derive(Default)]
649struct ReplayInsertions {
650    rebuilt: SmallVec<[(u64, u64); INLINE_REPLAY_INSERTIONS]>,
651    retained: ReplayIdentities,
652}
653
654impl ReplayInsertions {
655    fn clear(&mut self) {
656        if self.rebuilt.capacity() > MAX_REPLAY_SCRATCH_CAPACITY {
657            self.rebuilt = SmallVec::new();
658        } else {
659            self.rebuilt.clear();
660        }
661        clear_replay_set(&mut self.retained.keys);
662        clear_replay_set(&mut self.retained.logic_ids);
663    }
664}
665
666struct ReplayRange {
667    parent: NodeId,
668    next_sibling: Option<NodeId>,
669}
670
671pub(crate) struct ReplayReplaceContext {
672    parent_id: indextree::NodeId,
673    previous_sibling: Option<indextree::NodeId>,
674    previous_queue: Option<Vec<indextree::NodeId>>,
675}
676
677pub(crate) struct PreviousBuildNode {
678    pub(crate) node: ComponentNode,
679    same_parent: bool,
680    defer_consumer_change: bool,
681}
682
683fn has_frame_consumers(node: &ComponentNode) -> bool {
684    node.role == NodeRole::Layout
685        || node.interaction.is_some()
686        || !node.modifier.is_empty()
687        || node.layout_coordinate_space
688        || node.children_scale != 1.0
689}
690
691impl Default for ComponentTree {
692    fn default() -> Self {
693        Self::new()
694    }
695}
696
697impl ComponentTree {
698    /// Create a new ComponentTree
699    pub fn new() -> Self {
700        let tree = indextree::Arena::new();
701        let node_queue = Vec::new();
702        let metadatas = ComponentNodeMetaDatas::new();
703        Self {
704            tree,
705            node_queue,
706            metadatas,
707            layout_snapshots: LayoutSnapshotMap::default(),
708            frame_scene: scene::FrameScene::default(),
709            pointer_index: pointer::PointerIndex::default(),
710            accessibility_index: crate::accessibility::AccessibilityIndex::default(),
711            #[cfg(feature = "profiling")]
712            profiler_nodes_cache: HashMap::default(),
713            #[cfg(feature = "profiling")]
714            profiler_dirty_nodes: HashSet::default(),
715            scale_factor_bits: crate::dp::scale_factor().to_bits(),
716            replay_range: None,
717            replay_insertions: ReplayInsertions::default(),
718            replay_ancestry: replay::ReplayAncestry::default(),
719            replay_changed_parents: HashSet::default(),
720            node_ids_by_instance_key: HashMap::default(),
721            changed_frame_nodes: HashSet::default(),
722            removed_frame_nodes: HashSet::default(),
723            previous_structure_change_counts: [0; 2],
724            removed_focus_owners: HashSet::default(),
725            active_pointer_paths: HashMap::default(),
726            active_pointer_positions: HashMap::default(),
727            pointer_work: (0, HashSet::default()),
728            focus_owner: FocusOwner::new(),
729            pending_focused_input: VecDeque::new(),
730            last_global_rects: HashMap::default(),
731            observer_viewport: None,
732            position_observer_nodes: HashSet::default(),
733            window_observer_nodes: HashSet::default(),
734            pending_position_observers: HashSet::default(),
735            observer_spaces: HashMap::default(),
736            space_observers: HashMap::default(),
737            pending_globally_positioned_invocations: Vec::new(),
738        }
739    }
740
741    /// Clear the component tree
742    pub fn clear(&mut self) {
743        crate::runtime::remove_layout_structure(
744            &self.node_ids_by_instance_key.keys().copied().collect(),
745        );
746        self.removed_focus_owners
747            .extend(self.node_ids_by_instance_key.keys().copied());
748        self.removed_frame_nodes
749            .extend(self.node_ids_by_instance_key.values().copied());
750        self.changed_frame_nodes.clear();
751        self.previous_structure_change_counts = [0; 2];
752        #[cfg(feature = "profiling")]
753        {
754            self.profiler_nodes_cache.clear();
755            self.profiler_dirty_nodes.clear();
756        }
757        self.tree.clear();
758        self.metadatas.clear();
759        self.layout_snapshots.clear();
760        self.frame_scene = scene::FrameScene::default();
761        self.pointer_index = pointer::PointerIndex::default();
762        self.stop_accessibility_updates();
763        self.node_queue.clear();
764        self.replay_range = None;
765        self.replay_insertions.clear();
766        self.replay_ancestry = replay::ReplayAncestry::default();
767        self.replay_changed_parents.clear();
768        self.node_ids_by_instance_key.clear();
769        self.last_global_rects.clear();
770        self.observer_viewport = None;
771        self.position_observer_nodes.clear();
772        self.window_observer_nodes.clear();
773        self.pending_position_observers.clear();
774        self.observer_spaces.clear();
775        self.space_observers.clear();
776        self.pending_globally_positioned_invocations.clear();
777    }
778
779    /// Reset the entire component tree, including focus ownership state.
780    pub fn reset(&mut self) {
781        self.clear();
782        self.active_pointer_paths.clear();
783        self.active_pointer_positions.clear();
784        self.pointer_work.1.clear();
785        self.focus_owner.reset();
786        self.pending_focused_input.clear();
787        clipboard::clear_requests();
788    }
789
790    pub(crate) fn begin_frame_diagnostics(&mut self) {
791        self.pointer_work.1.clear();
792    }
793
794    pub(crate) fn refresh_scale_factor(&mut self) -> bool {
795        let current = crate::dp::scale_factor().to_bits();
796        let changed = self.scale_factor_bits != current;
797        self.scale_factor_bits = current;
798        changed
799    }
800
801    /// Get node by NodeId
802    pub(crate) fn get(&self, node_id: indextree::NodeId) -> Option<&ComponentNode> {
803        self.tree
804            .get(node_id)
805            .filter(|node| !node.is_removed())
806            .map(|node| node.get())
807    }
808
809    /// Get mutable node by NodeId
810    pub(crate) fn get_mut(&mut self, node_id: indextree::NodeId) -> Option<&mut ComponentNode> {
811        self.tree
812            .get_mut(node_id)
813            .filter(|node| !node.is_removed())
814            .map(|node| node.get_mut())
815    }
816
817    /// Find a node id by stable instance key.
818    pub(crate) fn find_node_id_by_instance_key(
819        &self,
820        instance_key: u64,
821    ) -> Option<indextree::NodeId> {
822        self.node_ids_by_instance_key.get(&instance_key).copied()
823    }
824
825    fn sync_accessibility(&mut self, root: NodeId, immediate: bool) -> usize {
826        self.accessibility_index
827            .invalidate_geometry(&self.metadatas.geometry_changed);
828        self.accessibility_index
829            .invalidate_placement_order(&self.metadatas.placement_order_changed);
830        self.accessibility_index
831            .invalidate(&self.metadatas.paint_changed, &HashSet::default());
832        if immediate {
833            self.accessibility_index
834                .update(&self.tree, &self.metadatas, root)
835        } else {
836            0
837        }
838    }
839
840    pub(crate) fn accessibility_update(
841        &mut self,
842        label: Option<&str>,
843        full: bool,
844    ) -> Option<accesskit::TreeUpdate> {
845        let root = self.tree.get_node_id_at(NonZero::new(1)?)?;
846        self.accessibility_index.set_label(label, root);
847        self.accessibility_index
848            .update(&self.tree, &self.metadatas, root);
849        self.accessibility_index.tree_update(
850            &self.tree,
851            self.focus_owner.active_component_node_id(),
852            full,
853        )
854    }
855
856    pub(crate) fn stop_accessibility_updates(&mut self) {
857        self.accessibility_index = crate::accessibility::AccessibilityIndex::default();
858    }
859
860    pub(crate) fn take_removed_focus_owners(&mut self) -> HashSet<u64> {
861        std::mem::take(&mut self.removed_focus_owners)
862    }
863
864    #[cfg(test)]
865    pub(crate) fn begin_replace_subtree_by_instance_key(
866        &mut self,
867        instance_key: u64,
868    ) -> Result<ReplayReplaceContext, ReplayReplaceError> {
869        let Some(target_node_id) = self.find_node_id_by_instance_key(instance_key) else {
870            return Err(ReplayReplaceError::NodeNotFound);
871        };
872        self.begin_replace_subtree(target_node_id)
873    }
874
875    pub(crate) fn begin_replace_subtree(
876        &mut self,
877        target_node_id: NodeId,
878    ) -> Result<ReplayReplaceContext, ReplayReplaceError> {
879        let node = self
880            .tree
881            .get(target_node_id)
882            .filter(|node| !node.is_removed())
883            .ok_or(ReplayReplaceError::NodeNotFound)?;
884        let parent_id = node
885            .parent()
886            .ok_or(ReplayReplaceError::RootNodeNotReplaceable)?;
887
888        let previous_sibling = target_node_id.previous_sibling(&self.tree);
889        let next_sibling = target_node_id.next_sibling(&self.tree);
890        self.replay_range = Some(ReplayRange {
891            parent: parent_id,
892            next_sibling,
893        });
894
895        self.queue_replay_children(parent_id, target_node_id, next_sibling);
896
897        let previous_queue =
898            (!self.node_queue.is_empty()).then(|| std::mem::take(&mut self.node_queue));
899        self.node_queue.push(parent_id);
900        Ok(ReplayReplaceContext {
901            parent_id,
902            previous_sibling,
903            previous_queue,
904        })
905    }
906
907    pub(crate) fn finish_replace_subtree(
908        &mut self,
909        context: ReplayReplaceContext,
910        result: &mut ReplayReplaceResult,
911    ) -> Result<(), ReplayReplaceError> {
912        result.clear();
913        let ReplayReplaceContext {
914            parent_id,
915            previous_sibling,
916            previous_queue,
917        } = context;
918        if let Some(previous_queue) = previous_queue {
919            self.restore_build_queue(previous_queue);
920        } else {
921            self.node_queue.clear();
922        }
923
924        self.detach_replay_suffix(parent_id);
925        let next_sibling = self
926            .replay_range
927            .take()
928            .and_then(|range| range.next_sibling);
929        let first_inserted = match previous_sibling {
930            Some(child) => child.next_sibling(&self.tree),
931            None => parent_id.first_child(&self.tree),
932        };
933        if first_inserted.is_none() || first_inserted == next_sibling {
934            self.replay_ancestry = replay::ReplayAncestry::default();
935            self.replay_insertions.clear();
936            return Err(ReplayReplaceError::ReplayDidNotCreateRoot);
937        }
938
939        std::mem::swap(&mut self.replay_insertions, &mut result.insertions);
940        let ReplayReplaceResult {
941            removed_instance_keys,
942            removed_instance_logic_ids,
943            insertions,
944        } = result;
945        let rebuilt = &insertions.rebuilt;
946        let retained = &insertions.retained;
947        let rebuilt_index = (!self.replay_ancestry.detached_roots.is_empty()
948            && rebuilt.len() > INLINE_REPLAY_INSERTIONS)
949            .then(|| {
950                let mut identities = ReplayIdentities::default();
951                for &(key, logic_id) in rebuilt {
952                    identities.insert(key, logic_id);
953                }
954                identities
955            });
956
957        let mut detached_node_ids = SmallVec::<[_; 8]>::new();
958        for &root in self.replay_ancestry.detached_roots.keys() {
959            detached_node_ids.extend(root.descendants(&self.tree));
960        }
961        for &old_id in &detached_node_ids {
962            let old = &self.tree[old_id];
963            let instance_key = old.get().instance_key;
964            let instance_logic_id = old.get().instance_logic_id;
965            let previous = self.metadatas.remove(&old_id);
966            let logic_id_survives = retained.logic_ids.contains(&instance_logic_id)
967                || rebuilt_index.as_ref().map_or_else(
968                    || rebuilt.iter().any(|&(_, id)| id == instance_logic_id),
969                    |index| index.logic_ids.contains(&instance_logic_id),
970                );
971            let key_survives = retained.keys.contains(&instance_key)
972                || rebuilt_index.as_ref().map_or_else(
973                    || rebuilt.iter().any(|&(key, _)| key == instance_key),
974                    |index| index.keys.contains(&instance_key),
975                );
976            if !logic_id_survives {
977                removed_instance_logic_ids.insert(instance_logic_id);
978            }
979            if !key_survives {
980                removed_instance_keys.insert(instance_key);
981            } else if instance_key != 0
982                && let Some(previous) = previous
983            {
984                let new_id = self.node_ids_by_instance_key[&instance_key];
985                let new = &self.tree[new_id];
986                let input_geometry_changed =
987                    !old.get().modifier.input_geometry_eq(&new.get().modifier);
988                let old_parent = old
989                    .parent()
990                    .or_else(|| self.replay_ancestry.detached_roots.get(&old_id).copied());
991                let parent_key = |id: NodeId| self.tree[id].get().instance_key;
992                let same_parent = old_parent.map(parent_key) == new.parent().map(parent_key);
993                let metadata = self.metadatas.entry_or_default(new_id);
994                metadata.computed_data = previous.computed_data;
995                metadata.draw_layout = previous.draw_layout;
996                metadata.rel_position = previous.rel_position;
997                metadata.placement_order = previous.placement_order;
998                metadata.base_abs_position = previous.base_abs_position;
999                metadata.local_to_window = previous.local_to_window;
1000                metadata.input_transforms = previous.input_transforms;
1001                metadata.event_clip_bounds = previous.event_clip_bounds;
1002                metadata.abs_position = previous.abs_position;
1003                metadata.prepared_layout = previous.prepared_layout;
1004                metadata.clip_regions = previous.clip_regions;
1005                metadata.geometry_initialized =
1006                    previous.geometry_initialized && !input_geometry_changed;
1007                metadata.clips_children = previous.clips_children;
1008                metadata.opacity = previous.opacity;
1009                metadata.has_shape_clipping = previous.has_shape_clipping;
1010                if same_parent && metadata.geometry_initialized {
1011                    self.metadatas.geometry_dirty.remove(&new_id);
1012                }
1013            }
1014            self.remove_position_observer(old_id);
1015            self.removed_frame_nodes.insert(old_id);
1016            self.changed_frame_nodes.remove(&old_id);
1017        }
1018        for key in removed_instance_keys.iter() {
1019            self.last_global_rects.remove(key);
1020            self.node_ids_by_instance_key.remove(key);
1021        }
1022        self.removed_focus_owners
1023            .extend(removed_instance_keys.iter().copied());
1024        crate::runtime::remove_layout_structure(removed_instance_keys);
1025        for &root in self.replay_ancestry.detached_roots.keys() {
1026            root.remove_subtree(&mut self.tree);
1027        }
1028        self.replay_ancestry.clear_detached();
1029
1030        self.replay_changed_parents.insert(parent_id);
1031        Ok(())
1032    }
1033
1034    pub(crate) fn finish_replay_round(&mut self) {
1035        let mut scheduled = std::mem::take(&mut self.replay_changed_parents);
1036        // Flattened layout children must be current before refreshing their parent.
1037        let mut pending: BinaryHeap<_> = scheduled
1038            .iter()
1039            .map(|&id| (id.ancestors(&self.tree).count(), id))
1040            .collect();
1041        while let Some((depth, id)) = pending.pop() {
1042            #[cfg(feature = "profiling")]
1043            let previous_len = self.tree[id].get().subtree_len;
1044            let changed = self.refresh_changed_layout_children(id);
1045            let node = &self.tree[id];
1046            let propagate = changed && node.get().role == NodeRole::Composition;
1047            #[cfg(feature = "profiling")]
1048            let propagate = propagate || previous_len != node.get().subtree_len;
1049            if propagate
1050                && let Some(parent) = node.parent()
1051                && scheduled.insert(parent)
1052            {
1053                pending.push((depth - 1, parent));
1054            }
1055        }
1056        scheduled.clear();
1057        self.replay_changed_parents = scheduled;
1058        self.finish_replay_ancestry();
1059    }
1060
1061    pub(crate) fn replace_build_parent(&mut self, parent: Option<NodeId>) -> Option<Vec<NodeId>> {
1062        if self.node_queue.last().copied() == parent {
1063            None
1064        } else {
1065            Some(std::mem::replace(
1066                &mut self.node_queue,
1067                parent.into_iter().collect(),
1068            ))
1069        }
1070    }
1071
1072    pub(crate) fn restore_build_queue(&mut self, queue: Vec<NodeId>) {
1073        self.node_queue = queue;
1074    }
1075
1076    pub(crate) fn current_build_node_id(&self) -> Option<NodeId> {
1077        self.node_queue.last().copied()
1078    }
1079
1080    /// Get current node
1081    pub(crate) fn current_node(&self) -> Option<&ComponentNode> {
1082        self.node_queue
1083            .last()
1084            .and_then(|node_id| self.get(*node_id))
1085    }
1086
1087    /// Get mutable current node
1088    pub(crate) fn current_node_mut(&mut self) -> Option<&mut ComponentNode> {
1089        let node_id = self.node_queue.last()?;
1090        self.get_mut(*node_id)
1091    }
1092
1093    pub(crate) fn attach_retained_subtree(
1094        &mut self,
1095        instance_key: u64,
1096        instance_logic_id: u64,
1097    ) -> Option<NodeId> {
1098        let candidate = self.claim_replay_candidate(instance_key, |node| {
1099            node.instance_logic_id == instance_logic_id
1100        })?;
1101        let id = candidate.id;
1102        self.capture_reused_ancestry(id);
1103        self.attach_replayed_node(candidate, true);
1104        Some(id)
1105    }
1106
1107    /// Add a new node to the tree
1108    /// Nodes now store their intrinsic constraints in their metadata.
1109    /// The `node_component` itself primarily holds the layout policy, render
1110    /// policy, and handlers.
1111    pub(crate) fn add_node(&mut self, node_component: ComponentNode) -> indextree::NodeId {
1112        let instance_key = node_component.instance_key;
1113        let needs_geometry = node_component.role == NodeRole::Layout || self.node_queue.is_empty();
1114        let new_node_id = self.tree.new_node(node_component);
1115        if instance_key != 0 || self.node_queue.is_empty() {
1116            self.node_ids_by_instance_key
1117                .insert(instance_key, new_node_id);
1118        }
1119        if needs_geometry {
1120            self.metadatas.entry_or_default(new_node_id);
1121        }
1122        self.attach_build_node(new_node_id, false);
1123        new_node_id
1124    }
1125
1126    pub(crate) fn rebuild_node(
1127        &mut self,
1128        mut node: ComponentNode,
1129    ) -> (NodeId, Option<PreviousBuildNode>) {
1130        let reusable = self.claim_replay_candidate(node.instance_key, |old| {
1131            old.role == node.role && old.instance_logic_id == node.instance_logic_id
1132        });
1133        let Some(candidate) = reusable else {
1134            return (self.add_node(node), None);
1135        };
1136        let id = candidate.id;
1137
1138        self.preserve_replay_children(id);
1139        let same_parent = candidate.keeps_link || {
1140            let previous_parent = self.tree[id]
1141                .parent()
1142                .or_else(|| self.replay_ancestry.detached_roots.get(&id).copied());
1143            let new_parent = self.current_build_node_id();
1144            let parent_key = |id: NodeId| self.tree[id].get().instance_key;
1145            previous_parent.map(parent_key) == new_parent.map(parent_key)
1146        };
1147        self.remove_position_observer(id);
1148        let old = self.tree[id].get_mut();
1149        node.layout_children = old.layout_children.take();
1150        node.layout_children_dirty = old.layout_children_dirty;
1151        node.sibling_index = old.sibling_index;
1152        #[cfg(feature = "profiling")]
1153        {
1154            node.subtree_len = old.subtree_len;
1155        }
1156        let previous = std::mem::replace(old, node);
1157        let metadata = self.metadatas.get_mut(&id);
1158        let defer_consumer_change =
1159            candidate.keeps_link && metadata.is_none() && !has_frame_consumers(&previous);
1160        if let Some(metadata) = metadata {
1161            metadata.begin_accessibility_rebuild();
1162        }
1163        if defer_consumer_change {
1164            self.enter_build_node(id, false, false);
1165        } else {
1166            self.attach_replayed_node(candidate, false);
1167        }
1168        if previous.role == NodeRole::Layout {
1169            self.metadatas.mark_paint_dirty(id);
1170        } else {
1171            // Reusing a composition ID can leave retained drawing behind a
1172            // rebuilt overlay. Even an empty fragment may become drawable.
1173            for descendant in id.descendants(&self.tree) {
1174                if self.tree[descendant].get().role == NodeRole::Layout {
1175                    self.metadatas.mark_paint_dirty(descendant);
1176                }
1177            }
1178        }
1179        (
1180            id,
1181            Some(PreviousBuildNode {
1182                node: previous,
1183                same_parent,
1184                defer_consumer_change,
1185            }),
1186        )
1187    }
1188
1189    pub(crate) fn reconcile_build_node(&mut self, id: NodeId, previous: &PreviousBuildNode) {
1190        let metadata = self.metadatas.get_mut(&id);
1191        let node = self.tree[id].get();
1192        if previous.defer_consumer_change
1193            && (metadata.is_some()
1194                || has_frame_consumers(node)
1195                || previous.node.fn_name != node.fn_name)
1196        {
1197            self.changed_frame_nodes.insert(id);
1198        }
1199        let Some(metadata) = metadata else {
1200            return;
1201        };
1202        metadata.finish_accessibility_rebuild();
1203        let input_changed = !previous.node.modifier.input_geometry_eq(&node.modifier);
1204        metadata.geometry_initialized &= !input_changed;
1205        if input_changed
1206            || !previous.same_parent
1207            || previous.node.children_scale != node.children_scale
1208        {
1209            self.metadatas.mark_geometry_dirty(id);
1210        }
1211    }
1212
1213    fn attach_replayed_node(&mut self, candidate: replay::ReplayCandidate, retained: bool) {
1214        let replay::ReplayCandidate { id, keeps_link } = candidate;
1215        if keeps_link {
1216            self.enter_build_node(id, retained, true);
1217            return;
1218        }
1219        self.replay_ancestry.detached_roots.remove(&id);
1220        if let Some(parent) = self.tree[id].parent() {
1221            self.tree[parent].get_mut().layout_children_dirty = true;
1222        }
1223        id.detach(&mut self.tree);
1224        self.attach_build_node(id, retained);
1225    }
1226
1227    fn attach_build_node(&mut self, id: NodeId, retained: bool) {
1228        if let Some(&parent) = self.node_queue.last() {
1229            self.tree[parent].get_mut().layout_children_dirty = true;
1230            let next = self.next_replay_child(parent).or_else(|| {
1231                self.replay_range
1232                    .as_ref()
1233                    .filter(|range| parent == range.parent)
1234                    .and_then(|range| range.next_sibling)
1235            });
1236            if let Some(sibling) = next {
1237                sibling.insert_before(id, &mut self.tree);
1238            } else {
1239                parent.append(id, &mut self.tree);
1240            }
1241        }
1242        self.enter_build_node(id, retained, true);
1243    }
1244
1245    fn enter_build_node(&mut self, id: NodeId, retained: bool, consumer_changed: bool) {
1246        if self.replay_range.is_some() {
1247            let node = self.tree[id].get();
1248            if retained {
1249                self.replay_insertions
1250                    .retained
1251                    .insert(node.instance_key, node.instance_logic_id);
1252            } else {
1253                self.replay_insertions
1254                    .rebuilt
1255                    .push((node.instance_key, node.instance_logic_id));
1256            }
1257        }
1258        if consumer_changed {
1259            self.changed_frame_nodes.insert(id);
1260        }
1261        self.node_queue.push(id);
1262    }
1263
1264    /// Pop the last node from the queue
1265    pub(crate) fn pop_node(&mut self) {
1266        if let Some(node_id) = self.node_queue.pop() {
1267            self.detach_replay_suffix(node_id);
1268            self.refresh_changed_layout_children(node_id);
1269            let node = self.tree[node_id].get();
1270            let interaction = node.interaction();
1271            let position_observers = &interaction.globally_positioned_handlers;
1272            let observes_window = position_observers
1273                .iter()
1274                .any(|handler| !matches!(handler, GloballyPositionedHandler::Size(_)));
1275            if let Some(metadata) = self.metadatas.get_mut(&node_id) {
1276                metadata.observes_size = position_observers
1277                    .iter()
1278                    .any(|handler| matches!(handler, GloballyPositionedHandler::Size(_)));
1279                metadata.observes_geometry = interaction.observes_input_geometry()
1280                    || observes_window
1281                    || node.layout_coordinate_space
1282                    || node.modifier.has_input_geometry();
1283            }
1284            if !position_observers.is_empty() {
1285                self.position_observer_nodes.insert(node_id);
1286                self.pending_position_observers.insert(node_id);
1287                if observes_window {
1288                    self.window_observer_nodes.insert(node_id);
1289                }
1290            }
1291        }
1292    }
1293
1294    fn refresh_changed_layout_children(&mut self, node_id: NodeId) -> bool {
1295        if !self.tree[node_id].get().layout_children_dirty {
1296            return false;
1297        }
1298        self.refresh_layout_children(node_id)
1299    }
1300
1301    fn refresh_layout_children(&mut self, node_id: indextree::NodeId) -> bool {
1302        #[cfg(feature = "profiling")]
1303        let previous_subtree_len = self.tree[node_id].get().subtree_len;
1304        let mut children = SmallVec::<[NodeId; 4]>::new();
1305        let mut shared_children = None;
1306        #[cfg(feature = "profiling")]
1307        let mut subtree_len = 1_usize;
1308        let first_child = node_id.first_child(&self.tree);
1309        let mut next_child = first_child;
1310        let mut index = NonZero::<usize>::MIN;
1311        while let Some(child_id) = next_child {
1312            next_child = child_id.next_sibling(&self.tree);
1313            if self.tree[child_id]
1314                .get_mut()
1315                .sibling_index
1316                .replace(index)
1317                .is_some_and(|previous| previous != index)
1318            {
1319                self.metadatas.placement_order_dirty.insert(child_id);
1320                self.metadatas.mark_geometry_dirty(child_id);
1321            }
1322            index = index.saturating_add(1);
1323            let child = self.tree[child_id].get();
1324            #[cfg(feature = "profiling")]
1325            {
1326                subtree_len = subtree_len.saturating_add(child.subtree_len);
1327            }
1328            if child.role == NodeRole::Layout {
1329                children.push(child_id);
1330            } else {
1331                let descendants = direct_layout_children(child_id, &self.tree);
1332                if first_child == Some(child_id) && next_child.is_none() {
1333                    shared_children = Some(descendants);
1334                } else {
1335                    children.extend_from_slice(&descendants);
1336                }
1337            }
1338        }
1339        let child_ids = shared_children.as_deref().unwrap_or(children.as_slice());
1340        let node_ref = &self.tree[node_id];
1341        let node = node_ref.get();
1342        let changed = node.layout_children.as_deref() != Some(child_ids);
1343        if changed && (node.role == NodeRole::Layout || node_ref.parent().is_none()) {
1344            self.frame_scene.invalidate_structure(node_id);
1345            crate::runtime::record_layout_structure(
1346                node.instance_key,
1347                child_ids.iter().map(|id| self.tree[*id].get().instance_key),
1348            );
1349        }
1350        let node = self.tree[node_id].get_mut();
1351        node.layout_children_dirty = false;
1352        #[cfg(feature = "profiling")]
1353        {
1354            node.subtree_len = subtree_len;
1355        }
1356        if changed {
1357            node.layout_children =
1358                Some(shared_children.unwrap_or_else(|| node::LayoutChildren::new(&children)));
1359        }
1360        let propagate = changed && node.role == NodeRole::Composition;
1361        #[cfg(feature = "profiling")]
1362        let propagate = propagate || previous_subtree_len != node.subtree_len;
1363        if propagate && let Some(parent) = self.tree[node_id].parent() {
1364            self.tree[parent].get_mut().layout_children_dirty = true;
1365        }
1366        changed
1367    }
1368
1369    /// Get a reference to the underlying tree structure.
1370    ///
1371    /// This is used for accessibility tree building and other introspection
1372    /// needs.
1373    pub(crate) fn tree(&self) -> &indextree::Arena<ComponentNode> {
1374        &self.tree
1375    }
1376
1377    /// Get a reference to the node metadatas.
1378    ///
1379    /// This is used for accessibility tree building and other introspection
1380    /// needs.
1381    #[cfg(any(
1382        test,
1383        feature = "testing",
1384        all(feature = "headless", not(target_family = "wasm")),
1385        feature = "debug-dirty-overlay"
1386    ))]
1387    pub(crate) fn metadatas(&self) -> &ComponentNodeMetaDatas {
1388        &self.metadatas
1389    }
1390
1391    pub(crate) fn metadatas_mut(&mut self) -> &mut ComponentNodeMetaDatas {
1392        &mut self.metadatas
1393    }
1394
1395    pub(crate) fn clear_layout_snapshots(&mut self) {
1396        self.layout_snapshots.clear();
1397    }
1398
1399    fn remove_layout_snapshots(&mut self, keys: &HashSet<u64>) {
1400        if keys.is_empty() {
1401            return;
1402        }
1403        for key in keys {
1404            self.layout_snapshots.remove(key);
1405        }
1406    }
1407
1408    fn defer_unplaced_layout_paths(
1409        &mut self,
1410        keys: impl Iterator<Item = u64>,
1411        placement_only: bool,
1412    ) {
1413        if self.layout_snapshots.entries.is_empty() {
1414            return;
1415        }
1416        let mut visited = HashSet::default();
1417        for key in keys {
1418            let Some(&node_id) = self.node_ids_by_instance_key.get(&key) else {
1419                continue;
1420            };
1421            let mut child = None;
1422            for id in node_id.ancestors(&self.tree) {
1423                let node = &self.tree[id];
1424                if node.get().role != NodeRole::Layout && node.parent().is_some() {
1425                    continue;
1426                }
1427                if self
1428                    .metadatas
1429                    .get(&id)
1430                    .is_some_and(|metadata| metadata.abs_position.is_some())
1431                {
1432                    break;
1433                }
1434                let key = node.get().instance_key;
1435                if self.layout_snapshots.entries.contains_key(&key) {
1436                    let invalidation = self.layout_snapshots.deferred.entry(key).or_default();
1437                    if let Some(child) = child {
1438                        if !invalidation.measure {
1439                            invalidation.children.insert(child);
1440                        }
1441                    } else if placement_only {
1442                        invalidation.placement = true;
1443                    } else {
1444                        invalidation.measure = true;
1445                        invalidation.children.clear();
1446                    }
1447                }
1448                if !visited.insert(id) {
1449                    break;
1450                }
1451                child = Some(key);
1452            }
1453        }
1454    }
1455
1456    pub(crate) fn focus_owner(&self) -> &FocusOwner {
1457        &self.focus_owner
1458    }
1459
1460    pub(crate) fn focus_owner_mut(&mut self) -> &mut FocusOwner {
1461        &mut self.focus_owner
1462    }
1463
1464    pub(crate) fn has_pending_focus_reveal(&self) -> bool {
1465        self.focus_owner.has_pending_reveal()
1466    }
1467
1468    pub(crate) fn accessibility_dispatch_context(
1469        &mut self,
1470    ) -> (&ComponentNodeTree, &ComponentNodeMetaDatas, &mut FocusOwner) {
1471        (&self.tree, &self.metadatas, &mut self.focus_owner)
1472    }
1473
1474    fn remove_position_observer(&mut self, node_id: NodeId) {
1475        self.position_observer_nodes.remove(&node_id);
1476        self.window_observer_nodes.remove(&node_id);
1477        self.pending_position_observers.remove(&node_id);
1478        self.forget_observer_space(node_id);
1479    }
1480
1481    fn forget_observer_space(&mut self, node_id: NodeId) {
1482        if let Some(space) = self.observer_spaces.remove(&node_id)
1483            && let Some(observers) = self.space_observers.get_mut(&space)
1484        {
1485            observers.remove(&node_id);
1486            if observers.is_empty() {
1487                self.space_observers.remove(&space);
1488            }
1489        }
1490    }
1491
1492    fn sync_observer_space(&mut self, node_id: NodeId) {
1493        let space = self.tree.get(node_id).and_then(|node| {
1494            node.get()
1495                .interaction()
1496                .globally_positioned_handlers
1497                .iter()
1498                .any(|handler| matches!(handler, GloballyPositionedHandler::InSpace(_)))
1499                .then(|| {
1500                    node_id.ancestors(&self.tree).find_map(|id| {
1501                        let node = self.tree[id].get();
1502                        (node.role == NodeRole::Layout && node.layout_coordinate_space)
1503                            .then_some(node.instance_key)
1504                    })
1505                })
1506                .flatten()
1507        });
1508        if self.observer_spaces.get(&node_id).copied() == space {
1509            return;
1510        }
1511        self.forget_observer_space(node_id);
1512        if let Some(space) = space {
1513            self.observer_spaces.insert(node_id, space);
1514            self.space_observers
1515                .entry(space)
1516                .or_default()
1517                .insert(node_id);
1518        }
1519    }
1520
1521    fn collect_globally_positioned_invocations(&mut self, viewport: PxSize) {
1522        let mut candidates = std::mem::take(&mut self.pending_position_observers);
1523        for &node_id in &candidates {
1524            self.sync_observer_space(node_id);
1525        }
1526        if self.observer_viewport != Some(viewport) {
1527            candidates.extend(
1528                self.window_observer_nodes
1529                    .iter()
1530                    .filter(|id| {
1531                        self.tree.get(**id).is_some_and(|node| {
1532                            node.get()
1533                                .interaction()
1534                                .globally_positioned_handlers
1535                                .iter()
1536                                .any(|handler| {
1537                                    matches!(handler, GloballyPositionedHandler::Visible(_))
1538                                })
1539                        })
1540                    })
1541                    .copied(),
1542            );
1543            self.observer_viewport = Some(viewport);
1544        }
1545        let changed = &self.metadatas.geometry_changed;
1546        // Counter-translated descendants can retain window geometry while
1547        // changing their position within a moving coordinate space.
1548        if self.space_observers.len() < changed.len() {
1549            for (space, observers) in &self.space_observers {
1550                if self
1551                    .node_ids_by_instance_key
1552                    .get(space)
1553                    .is_some_and(|id| changed.contains(id))
1554                {
1555                    candidates.extend(observers.iter().copied());
1556                }
1557            }
1558        } else {
1559            for &node_id in changed {
1560                if let Some(observers) = self
1561                    .tree
1562                    .get(node_id)
1563                    .and_then(|node| self.space_observers.get(&node.get().instance_key))
1564                {
1565                    candidates.extend(observers.iter().copied());
1566                }
1567            }
1568        }
1569        if self.window_observer_nodes.len() < changed.len() {
1570            candidates.extend(
1571                self.window_observer_nodes
1572                    .iter()
1573                    .filter(|id| changed.contains(id))
1574                    .copied(),
1575            );
1576        } else {
1577            candidates.extend(
1578                changed
1579                    .iter()
1580                    .filter(|id| self.window_observer_nodes.contains(id))
1581                    .copied(),
1582            );
1583        }
1584        candidates.extend(self.metadatas.size_changed.iter().copied());
1585        let observer_candidates = candidates.len();
1586        let mut invocations = Vec::new();
1587        for node_id in candidates {
1588            let Some(node_ref) = self.tree.get(node_id) else {
1589                continue;
1590            };
1591            let node = node_ref.get();
1592            if node.interaction().globally_positioned_handlers.is_empty() {
1593                continue;
1594            }
1595            let Some(metadata) = self.metadatas.get(&node_id) else {
1596                continue;
1597            };
1598            let Some(position) = metadata.abs_position else {
1599                if let Some(previous) = self.last_global_rects.remove(&node.instance_key) {
1600                    let mut geometry = previous.geometry;
1601                    geometry.visible = None;
1602                    for handler in &node.interaction().globally_positioned_handlers {
1603                        if matches!(handler, GloballyPositionedHandler::Visible(_))
1604                            && previous.geometry.visible.is_some()
1605                        {
1606                            invocations.push((*handler, geometry));
1607                        }
1608                    }
1609                }
1610                continue;
1611            };
1612            let size = metadata.computed_data.unwrap_or(ComputedData::ZERO);
1613            let rect = PxRect {
1614                x: position.x_px(),
1615                y: position.y_px(),
1616                width: size.width,
1617                height: size.height,
1618            };
1619            let transform = metadata
1620                .local_to_window
1621                .unwrap_or_else(|| crate::LocalToWindow::new(1.0, position).unwrap());
1622            let precise = transform.map_rect(crate::FloatRect::from_position_size(
1623                PxPosition::ZERO,
1624                PxSize::new(size.width, size.height),
1625            ));
1626            let geometry = node::GeometryObservation {
1627                rounded: if transform.scale() == 1.0 {
1628                    rect
1629                } else {
1630                    precise.outward()
1631                },
1632                precise,
1633                visible: precise
1634                    .intersection(metadata.event_clip_bounds.unwrap_or(precise))
1635                    .and_then(|bounds| {
1636                        bounds.intersection(crate::FloatRect::from_position_size(
1637                            PxPosition::ZERO,
1638                            viewport,
1639                        ))
1640                    }),
1641                transform,
1642                coordinates: crate::LayoutCoordinates {
1643                    local_size: PxSize::new(size.width, size.height),
1644                    local_to_window: transform,
1645                },
1646                in_space: if node
1647                    .interaction()
1648                    .globally_positioned_handlers
1649                    .iter()
1650                    .any(|handler| matches!(handler, GloballyPositionedHandler::InSpace(_)))
1651                {
1652                    layout_rect_in_space(&self.tree, &self.metadatas, node_id).unwrap_or(precise)
1653                } else {
1654                    precise
1655                },
1656            };
1657            let previous = self.last_global_rects.get(&node.instance_key);
1658            for handler in &node.interaction().globally_positioned_handlers {
1659                if previous.is_none_or(|previous| {
1660                    handler.changed(previous.geometry, geometry)
1661                        || !previous.handlers.contains(handler)
1662                }) {
1663                    invocations.push((*handler, geometry));
1664                }
1665            }
1666            self.last_global_rects.insert(
1667                node.instance_key,
1668                GlobalPositionSnapshot {
1669                    geometry,
1670                    handlers: node
1671                        .interaction()
1672                        .globally_positioned_handlers
1673                        .iter()
1674                        .copied()
1675                        .collect(),
1676                },
1677            );
1678        }
1679        tracing::debug!(
1680            target: "tessera_ui::frame_cost",
1681            callbacks = invocations.len(),
1682            candidates = observer_candidates,
1683            size_changes = self.metadatas.size_changed.len(),
1684            "position_observer_work"
1685        );
1686        self.pending_globally_positioned_invocations
1687            .extend(invocations);
1688    }
1689
1690    pub(crate) fn take_pending_globally_positioned_invocations(
1691        &mut self,
1692    ) -> Vec<(GloballyPositionedHandler, node::GeometryObservation)> {
1693        std::mem::take(&mut self.pending_globally_positioned_invocations)
1694    }
1695
1696    pub(crate) fn take_pending_focus_callback_invocations(
1697        &mut self,
1698    ) -> Vec<PendingFocusCallbackInvocation> {
1699        let notifications = self.focus_owner.take_pending_notifications();
1700        let mut invocations = Vec::new();
1701
1702        for notification in notifications {
1703            let Some(node_id) = self
1704                .focus_owner
1705                .component_node_id_of(notification.handle_id)
1706            else {
1707                continue;
1708            };
1709            let Some(node_ref) = self.tree.get(node_id) else {
1710                continue;
1711            };
1712            let node = node_ref.get();
1713
1714            if notification.changed
1715                && let Some(handler) = &node.interaction().focus_changed_handler
1716            {
1717                invocations.push(PendingFocusCallbackInvocation::new(
1718                    *handler,
1719                    notification.state,
1720                    notification.direction,
1721                ));
1722            }
1723            if let Some(handler) = &node.interaction().focus_event_handler {
1724                invocations.push(PendingFocusCallbackInvocation::new(
1725                    *handler,
1726                    notification.state,
1727                    notification.direction,
1728                ));
1729            }
1730        }
1731
1732        invocations
1733    }
1734
1735    /// Collect per-node metadata for profiling output.
1736    #[cfg(feature = "profiling")]
1737    pub fn profiler_nodes(&mut self) -> Vec<NodeMeta> {
1738        self.profiler_dirty_nodes
1739            .extend(std::mem::take(&mut self.metadatas.profiling_dirty));
1740        self.profiler_dirty_nodes
1741            .extend(self.metadatas.geometry_changed.iter().copied());
1742        for node_id in std::mem::take(&mut self.profiler_dirty_nodes) {
1743            let Some(node) = self.tree.get(node_id).filter(|node| !node.is_removed()) else {
1744                self.profiler_nodes_cache.remove(&node_id);
1745                continue;
1746            };
1747            let metadata = self.metadatas.get(&node_id);
1748            self.profiler_nodes_cache.insert(
1749                node_id,
1750                NodeMeta {
1751                    node_id: node_id.to_string(),
1752                    parent: node.parent().map(|id| id.to_string()),
1753                    fn_name: Some(node.get().fn_name.to_owned()),
1754                    abs_pos: metadata
1755                        .and_then(|metadata| metadata.abs_position)
1756                        .map(|position| (position.x_px().0, position.y_px().0)),
1757                    size: metadata
1758                        .and_then(|metadata| metadata.computed_data)
1759                        .map(|size| (size.width.0, size.height.0)),
1760                    layout_cache_hit: metadata.map(|metadata| metadata.layout_cache_hit),
1761                },
1762            );
1763        }
1764        self.profiler_nodes_cache.values().cloned().collect()
1765    }
1766
1767    /// Dispatches pointer input across the tree and returns the window requests
1768    /// raised by the handlers (currently the hover cursor icon).
1769    fn dispatch_pointer_input(
1770        &mut self,
1771        root_node: indextree::NodeId,
1772        pointer_changes: &mut [PointerChange],
1773        cursor_position: &mut Option<PxPosition>,
1774        modifiers: winit::keyboard::ModifiersState,
1775    ) -> WindowRequests {
1776        let mut window_requests = WindowRequests::default();
1777        self.pointer_index.invalidate(
1778            &self.tree,
1779            &self.changed_frame_nodes,
1780            &self.removed_frame_nodes,
1781        );
1782        self.pointer_work.0 = self.pointer_index.update(&self.tree, &self.metadatas) as u64;
1783        self.pointer_index.begin_dispatch();
1784        self.active_pointer_paths.retain(|_, path| {
1785            path.last().is_some_and(|key| {
1786                self.node_ids_by_instance_key.get(key).is_some_and(|id| {
1787                    self.metadatas
1788                        .get(id)
1789                        .is_some_and(|metadata| metadata.abs_position.is_some())
1790                })
1791            })
1792        });
1793        self.active_pointer_positions
1794            .retain(|pointer, _| self.active_pointer_paths.contains_key(pointer));
1795        let dirty = crate::runtime::take_pointer_dirty_nodes();
1796        let mut selected: HashSet<_> = dirty
1797            .into_iter()
1798            .filter_map(|key| self.node_ids_by_instance_key.get(&key).copied())
1799            .collect();
1800        for path in self.active_pointer_paths.values() {
1801            selected.extend(
1802                path.iter()
1803                    .filter_map(|key| self.node_ids_by_instance_key.get(key).copied()),
1804            );
1805        }
1806        for change in pointer_changes.iter() {
1807            selected.extend(self.pointer_index.sample_targets(
1808                &self.tree,
1809                &self.metadatas,
1810                change.resolved_position(*cursor_position),
1811            ));
1812            selected.extend(self.pointer_index.hit_path(
1813                &self.tree,
1814                &self.metadatas,
1815                change.resolved_position(*cursor_position),
1816            ));
1817        }
1818        let final_position = *cursor_position;
1819        let path = self
1820            .pointer_index
1821            .hit_path(&self.tree, &self.metadatas, final_position);
1822        window_requests.cursor_icon = path
1823            .into_iter()
1824            .rev()
1825            .find_map(|id| {
1826                let node = self.tree.get(id)?.get();
1827                let metadata = self.metadatas.get(&id)?;
1828                resolve_node_hover_cursor_icon(
1829                    node,
1830                    metadata.base_abs_position?,
1831                    metadata.computed_data?,
1832                    final_position?,
1833                    metadata
1834                        .local_to_window
1835                        .map_or(1.0, |transform| transform.scale()),
1836                    metadata.input_transforms.as_deref(),
1837                )
1838            })
1839            .unwrap_or_default();
1840        let plan = self.pointer_index.plan(
1841            &self.tree,
1842            &self.metadatas,
1843            final_position,
1844            selected.into_iter(),
1845        );
1846        self.pointer_work
1847            .1
1848            .extend(plan.nodes.values().map(|node| node.context.instance_key));
1849        for node in plan.nodes.values() {
1850            crate::runtime::clear_phase_state_reads(
1851                node.context.instance_key,
1852                crate::runtime::ReadPhase::Pointer,
1853            );
1854        }
1855        let mut last_position = None;
1856        for change in pointer_changes {
1857            if matches!(&change.content, CursorEventContent::Pressed(_)) {
1858                self.focus_owner.observe_pointer_press(change.timestamp);
1859            }
1860            if matches!(change.content, CursorEventContent::Scroll(_))
1861                && change.scroll_discrete.is_none()
1862            {
1863                change.scroll_discrete = Some(change.is_discrete_scroll());
1864            }
1865            if matches!(
1866                &change.content,
1867                CursorEventContent::Scroll(scroll)
1868                    if scroll.delta_x.is_finite()
1869                        && scroll.delta_y.is_finite()
1870                        && (scroll.delta_x != 0.0 || scroll.delta_y != 0.0)
1871            ) {
1872                self.focus_owner.cancel_pending_reveal_for_scroll();
1873            }
1874            let mut position = change.resolved_position(final_position);
1875            last_position = Some(position);
1876            self.dispatch_pointer_sample(
1877                root_node,
1878                std::slice::from_mut(change),
1879                &mut position,
1880                modifiers,
1881                &plan,
1882                &mut window_requests,
1883            );
1884        }
1885        if last_position != Some(final_position) {
1886            self.dispatch_pointer_sample(
1887                root_node,
1888                &mut [],
1889                cursor_position,
1890                modifiers,
1891                &plan,
1892                &mut window_requests,
1893            );
1894        }
1895        window_requests
1896    }
1897
1898    fn dispatch_pointer_sample(
1899        &mut self,
1900        _root_node: indextree::NodeId,
1901        pointer_changes: &mut [PointerChange],
1902        cursor_position: &mut Option<PxPosition>,
1903        modifiers: winit::keyboard::ModifiersState,
1904        plan: &PointerDispatchPlan,
1905        window_requests: &mut WindowRequests,
1906    ) {
1907        let node_ids_preorder = &plan.preorder;
1908        let node_ids_postorder = &plan.postorder;
1909        let pointer_change_paths = build_pointer_change_paths(
1910            &self.pointer_index,
1911            &self.tree,
1912            &self.metadatas,
1913            pointer_changes,
1914            *cursor_position,
1915            &mut self.active_pointer_paths,
1916        );
1917        for change in pointer_changes.iter() {
1918            if self.active_pointer_paths.contains_key(&change.pointer_id) {
1919                self.active_pointer_positions.insert(
1920                    change.pointer_id,
1921                    change.resolved_position(*cursor_position),
1922                );
1923            } else {
1924                self.active_pointer_positions.remove(&change.pointer_id);
1925            }
1926        }
1927
1928        for node_id in node_ids_preorder.iter().copied() {
1929            let Some(node) = self.tree.get(node_id).map(|n| n.get()) else {
1930                continue;
1931            };
1932            let mut dispatch_ctx = PointerInputDispatchContext {
1933                tree: &self.tree,
1934                metadatas: &self.metadatas,
1935                cursor_position: &mut *cursor_position,
1936                pointer_changes: &mut *pointer_changes,
1937                pointer_change_paths: &pointer_change_paths,
1938                nodes: &plan.nodes,
1939                active_pointer_paths: &self.active_pointer_paths,
1940                active_pointer_positions: &self.active_pointer_positions,
1941                modifiers,
1942                window_requests: &mut *window_requests,
1943                focus_owner: &mut self.focus_owner,
1944            };
1945            dispatch_pointer_modifiers_for_node_pass(
1946                &mut dispatch_ctx,
1947                node_id,
1948                PointerEventPass::Initial,
1949            );
1950            let handlers = &node.interaction().pointer_preview_handlers;
1951            for handler in handlers {
1952                let mut dispatch_ctx = PointerInputDispatchContext {
1953                    tree: &self.tree,
1954                    metadatas: &self.metadatas,
1955                    cursor_position: &mut *cursor_position,
1956                    pointer_changes: &mut *pointer_changes,
1957                    pointer_change_paths: &pointer_change_paths,
1958                    nodes: &plan.nodes,
1959                    active_pointer_paths: &self.active_pointer_paths,
1960                    active_pointer_positions: &self.active_pointer_positions,
1961                    modifiers,
1962                    window_requests: &mut *window_requests,
1963                    focus_owner: &mut self.focus_owner,
1964                };
1965                run_pointer_handler_for_node(
1966                    &mut dispatch_ctx,
1967                    node_id,
1968                    PointerEventPass::Initial,
1969                    handler.as_ref(),
1970                );
1971            }
1972        }
1973
1974        for node_id in node_ids_postorder.iter().copied() {
1975            let Some(node) = self.tree.get(node_id).map(|n| n.get()) else {
1976                continue;
1977            };
1978            let mut dispatch_ctx = PointerInputDispatchContext {
1979                tree: &self.tree,
1980                metadatas: &self.metadatas,
1981                cursor_position: &mut *cursor_position,
1982                pointer_changes: &mut *pointer_changes,
1983                pointer_change_paths: &pointer_change_paths,
1984                nodes: &plan.nodes,
1985                active_pointer_paths: &self.active_pointer_paths,
1986                active_pointer_positions: &self.active_pointer_positions,
1987                modifiers,
1988                window_requests: &mut *window_requests,
1989                focus_owner: &mut self.focus_owner,
1990            };
1991            dispatch_pointer_modifiers_for_node_pass(
1992                &mut dispatch_ctx,
1993                node_id,
1994                PointerEventPass::Main,
1995            );
1996            let handlers = &node.interaction().pointer_handlers;
1997            for handler in handlers {
1998                let mut dispatch_ctx = PointerInputDispatchContext {
1999                    tree: &self.tree,
2000                    metadatas: &self.metadatas,
2001                    cursor_position: &mut *cursor_position,
2002                    pointer_changes: &mut *pointer_changes,
2003                    pointer_change_paths: &pointer_change_paths,
2004                    nodes: &plan.nodes,
2005                    active_pointer_paths: &self.active_pointer_paths,
2006                    active_pointer_positions: &self.active_pointer_positions,
2007                    modifiers,
2008                    window_requests: &mut *window_requests,
2009                    focus_owner: &mut self.focus_owner,
2010                };
2011                run_pointer_handler_for_node(
2012                    &mut dispatch_ctx,
2013                    node_id,
2014                    PointerEventPass::Main,
2015                    handler.as_ref(),
2016                );
2017            }
2018        }
2019
2020        for node_id in node_ids_preorder.iter().copied() {
2021            let Some(node) = self.tree.get(node_id).map(|n| n.get()) else {
2022                continue;
2023            };
2024            let mut dispatch_ctx = PointerInputDispatchContext {
2025                tree: &self.tree,
2026                metadatas: &self.metadatas,
2027                cursor_position: &mut *cursor_position,
2028                pointer_changes: &mut *pointer_changes,
2029                pointer_change_paths: &pointer_change_paths,
2030                nodes: &plan.nodes,
2031                active_pointer_paths: &self.active_pointer_paths,
2032                active_pointer_positions: &self.active_pointer_positions,
2033                modifiers,
2034                window_requests: &mut *window_requests,
2035                focus_owner: &mut self.focus_owner,
2036            };
2037            dispatch_pointer_modifiers_for_node_pass(
2038                &mut dispatch_ctx,
2039                node_id,
2040                PointerEventPass::Final,
2041            );
2042            let handlers = &node.interaction().pointer_final_handlers;
2043            for handler in handlers {
2044                let mut dispatch_ctx = PointerInputDispatchContext {
2045                    tree: &self.tree,
2046                    metadatas: &self.metadatas,
2047                    cursor_position: &mut *cursor_position,
2048                    pointer_changes: &mut *pointer_changes,
2049                    pointer_change_paths: &pointer_change_paths,
2050                    nodes: &plan.nodes,
2051                    active_pointer_paths: &self.active_pointer_paths,
2052                    active_pointer_positions: &self.active_pointer_positions,
2053                    modifiers,
2054                    window_requests: &mut *window_requests,
2055                    focus_owner: &mut self.focus_owner,
2056                };
2057                run_pointer_handler_for_node(
2058                    &mut dispatch_ctx,
2059                    node_id,
2060                    PointerEventPass::Final,
2061                    handler.as_ref(),
2062                );
2063            }
2064        }
2065    }
2066
2067    fn dispatch_keyboard_event(
2068        &mut self,
2069        root_node: NodeId,
2070        queued: QueuedInput,
2071        window_requests: &mut WindowRequests,
2072    ) -> Option<FocusDirection> {
2073        let path = collect_focus_chain_node_ids(root_node, &self.tree, &self.focus_owner);
2074        let mut events = vec![queued.event];
2075        let mut context = KeyboardInputDispatchContext {
2076            tree: &self.tree,
2077            metadatas: &self.metadatas,
2078            keyboard_events: &mut events,
2079            modifiers: queued.modifiers,
2080            ime_client: self.focus_owner.active_handle_id(),
2081            window_requests,
2082            focus_owner: &mut self.focus_owner,
2083        };
2084        for node_id in path.iter().copied() {
2085            let Some(node) = context.tree.get(node_id).map(|node| node.get()) else {
2086                continue;
2087            };
2088            for action in node.modifier.ordered_actions() {
2089                if let OrderedModifierAction::KeyboardPreviewInput(modifier) = action {
2090                    run_keyboard_input_for_node(&mut context, node_id, |input| {
2091                        modifier.on_keyboard_input(input)
2092                    });
2093                }
2094            }
2095            for handler in &node.interaction().keyboard_preview_handlers {
2096                run_keyboard_input_for_node(&mut context, node_id, handler.as_ref());
2097            }
2098        }
2099        for node_id in path.iter().rev().copied() {
2100            let Some(node) = context.tree.get(node_id).map(|node| node.get()) else {
2101                continue;
2102            };
2103            for action in node.modifier.ordered_actions() {
2104                if let OrderedModifierAction::KeyboardInput(modifier) = action {
2105                    run_keyboard_input_for_node(&mut context, node_id, |input| {
2106                        modifier.on_keyboard_input(input)
2107                    });
2108                }
2109            }
2110            for handler in &node.interaction().keyboard_handlers {
2111                run_keyboard_input_for_node(&mut context, node_id, handler.as_ref());
2112            }
2113        }
2114        dispatch_default_focus_keyboard_navigation(
2115            context.tree,
2116            context.keyboard_events,
2117            context.modifiers,
2118            context.focus_owner,
2119        )
2120    }
2121
2122    fn dispatch_clipboard_action(&mut self, root_node: NodeId, request: QueuedClipboardAction) {
2123        let path = collect_focus_chain_node_ids(root_node, &self.tree, &self.focus_owner);
2124        let mut response = None;
2125        for node_id in path.into_iter().rev() {
2126            let Some(context) = resolve_node_input_context(&self.tree, &self.metadatas, node_id)
2127            else {
2128                continue;
2129            };
2130            let node = self.tree.get(node_id).expect("focused node").get();
2131            for action in node.modifier.ordered_actions() {
2132                if let OrderedModifierAction::ClipboardInput(modifier) = action {
2133                    let boundary = nearest_replay_boundary_instance_key(node_id, &self.tree);
2134                    let _node = push_current_node_with_instance_logic_id(
2135                        node_id,
2136                        context.instance_logic_id,
2137                        context.fn_name,
2138                    );
2139                    let _instance = push_current_component_instance_key(boundary);
2140                    let _phase = push_phase(RuntimePhase::Input);
2141                    let _focus = bind_focus_owner(&mut self.focus_owner);
2142                    modifier.on_clipboard_input(ClipboardInput {
2143                        action: &request.action,
2144                        response: &mut response,
2145                    });
2146                    if response.is_some() {
2147                        break;
2148                    }
2149                }
2150            }
2151            if response.is_some() {
2152                break;
2153            }
2154        }
2155        (request.reply)(response.unwrap_or(ClipboardResponse::Ignored));
2156    }
2157
2158    fn dispatch_ime_input(
2159        &mut self,
2160        root_node: NodeId,
2161        events: &mut Vec<crate::ime_state::ImeEvent>,
2162        window_requests: &mut WindowRequests,
2163    ) {
2164        let path = collect_focus_chain_node_ids(root_node, &self.tree, &self.focus_owner);
2165        let client = self.focus_owner.active_handle_id();
2166        let mut pending = std::mem::take(events)
2167            .into_iter()
2168            .filter_map(|(source, event)| {
2169                source
2170                    .is_none_or(|source| Some(source) == client)
2171                    .then_some(event)
2172            })
2173            .collect();
2174        for node_id in path.iter().copied() {
2175            let Some(node) = self.tree.get(node_id).map(|node| node.get()) else {
2176                continue;
2177            };
2178            for action in node.modifier.ordered_actions() {
2179                if let OrderedModifierAction::ImePreviewInput(modifier) = action {
2180                    run_ime_input_for_node(
2181                        &self.tree,
2182                        &self.metadatas,
2183                        node_id,
2184                        &mut pending,
2185                        window_requests,
2186                        &mut self.focus_owner,
2187                        client,
2188                        |input| modifier.on_ime_input(input),
2189                    );
2190                }
2191            }
2192            for handler in &node.interaction().ime_preview_handlers {
2193                run_ime_input_for_node(
2194                    &self.tree,
2195                    &self.metadatas,
2196                    node_id,
2197                    &mut pending,
2198                    window_requests,
2199                    &mut self.focus_owner,
2200                    client,
2201                    handler.as_ref(),
2202                );
2203            }
2204        }
2205        for node_id in path.iter().rev().copied() {
2206            let Some(node) = self.tree.get(node_id).map(|node| node.get()) else {
2207                continue;
2208            };
2209            for action in node.modifier.ordered_actions() {
2210                if let OrderedModifierAction::ImeInput(modifier) = action {
2211                    run_ime_input_for_node(
2212                        &self.tree,
2213                        &self.metadatas,
2214                        node_id,
2215                        &mut pending,
2216                        window_requests,
2217                        &mut self.focus_owner,
2218                        client,
2219                        |input| modifier.on_ime_input(input),
2220                    );
2221                }
2222            }
2223            for handler in &node.interaction().ime_handlers {
2224                run_ime_input_for_node(
2225                    &self.tree,
2226                    &self.metadatas,
2227                    node_id,
2228                    &mut pending,
2229                    window_requests,
2230                    &mut self.focus_owner,
2231                    client,
2232                    handler.as_ref(),
2233                );
2234            }
2235        }
2236    }
2237
2238    fn queue_focused_input(
2239        &mut self,
2240        keyboard: Vec<QueuedInput>,
2241        ime: Vec<QueuedInput<crate::ime_state::ImeEvent>>,
2242    ) {
2243        self.pending_focused_input
2244            .extend(keyboard.into_iter().map(QueuedFocusedInput::Keyboard));
2245        self.pending_focused_input
2246            .extend(ime.into_iter().map(QueuedFocusedInput::Ime));
2247        self.pending_focused_input.extend(
2248            clipboard::take_requests()
2249                .into_iter()
2250                .map(QueuedFocusedInput::Clipboard),
2251        );
2252        self.pending_focused_input
2253            .make_contiguous()
2254            .sort_by_key(QueuedFocusedInput::sequence);
2255    }
2256
2257    fn dispatch_pending_focused_input(
2258        &mut self,
2259        root: NodeId,
2260        requests: &mut WindowRequests,
2261    ) -> Option<FocusDirection> {
2262        let mut events = std::mem::take(&mut self.pending_focused_input);
2263        let retry = if self.focus_owner.keyboard_is_deferred() {
2264            None
2265        } else {
2266            drain_pending_focused_input(&mut events, |event| {
2267                let cause = match &event {
2268                    QueuedFocusedInput::Ime(event) => {
2269                        let (client, event) = &event.event;
2270                        (client.is_none_or(|client| {
2271                            Some(client) == self.focus_owner.active_handle_id()
2272                        }) && !matches!(
2273                            event,
2274                            winit::event::Ime::Enabled | winit::event::Ime::Disabled
2275                        ))
2276                        .then_some(winit::event::ImeChangeCause::InputMethod)
2277                    }
2278                    _ => Some(winit::event::ImeChangeCause::Other),
2279                };
2280                let retry = match event {
2281                    QueuedFocusedInput::Keyboard(event) => {
2282                        self.dispatch_keyboard_event(root, event, requests)
2283                    }
2284                    QueuedFocusedInput::Clipboard(request) => {
2285                        self.dispatch_clipboard_action(root, request);
2286                        None
2287                    }
2288                    QueuedFocusedInput::Ime(event) => {
2289                        self.dispatch_ime_input(root, &mut vec![event.event], requests);
2290                        None
2291                    }
2292                };
2293                if let (Some(request), Some(cause)) = (&mut requests.ime_request, cause) {
2294                    request.change_cause = cause;
2295                }
2296                self.focus_owner.commit_pending();
2297                (retry, self.focus_owner.keyboard_is_deferred())
2298            })
2299        };
2300        self.pending_focused_input = events;
2301        if retry.is_some()
2302            || self.focus_owner.keyboard_is_deferred()
2303            || self.focus_owner.has_pending_navigation()
2304        {
2305            requests.ime_request = None;
2306        } else {
2307            let cause = requests
2308                .ime_request
2309                .as_ref()
2310                .map_or(winit::event::ImeChangeCause::Other, |request| {
2311                    request.change_cause
2312                });
2313            self.dispatch_ime_input(root, &mut Vec::new(), requests);
2314            if let Some(request) = &mut requests.ime_request {
2315                request.change_cause = cause;
2316            }
2317        }
2318        retry
2319    }
2320
2321    pub(crate) fn flush_focused_input(
2322        &mut self,
2323        cursor: &mut crate::cursor::CursorState,
2324        modifiers: winit::keyboard::ModifiersState,
2325        keyboard: Vec<QueuedInput>,
2326        ime: Vec<QueuedInput<crate::ime_state::ImeEvent>>,
2327    ) -> WindowRequests {
2328        self.queue_focused_input(keyboard, ime);
2329        let Some(root) = self.tree.get_node_id_at(NonZero::new(1).unwrap()) else {
2330            return WindowRequests::default();
2331        };
2332        if self
2333            .metadatas
2334            .get(&root)
2335            .is_none_or(|metadata| metadata.computed_data.is_none())
2336        {
2337            return WindowRequests::default();
2338        }
2339        let mut pointer = cursor.take_events();
2340        let mut position = cursor.position();
2341        let mut requests =
2342            self.dispatch_pointer_input(root, &mut pointer, &mut position, modifiers);
2343        self.focus_owner.commit_pending();
2344        if self.focus_owner.keyboard_is_deferred() || self.focus_owner.has_pending_navigation() {
2345            requests.ime_request = None;
2346            return requests;
2347        }
2348        let retry = self.dispatch_pending_focused_input(root, &mut requests);
2349        self.focus_owner.retain_navigation_retry(retry);
2350        requests
2351    }
2352
2353    fn clear_frame_structure_changes(&mut self) {
2354        let live_nodes = self.node_ids_by_instance_key.len();
2355        for (nodes, previous_count) in
2356            [&mut self.changed_frame_nodes, &mut self.removed_frame_nodes]
2357                .into_iter()
2358                .zip(&mut self.previous_structure_change_counts)
2359        {
2360            // Large journals pay for retained capacity only when the work recurs.
2361            let recent_count = nodes
2362                .len()
2363                .min(std::mem::replace(previous_count, nodes.len()));
2364            let capacity_limit = recent_count.min(live_nodes).max(64).saturating_mul(2);
2365            if nodes.capacity() > capacity_limit {
2366                *nodes = HashSet::default();
2367            } else {
2368                nodes.clear();
2369            }
2370        }
2371    }
2372
2373    /// Compute the ComponentTree into a render graph
2374    ///
2375    /// This method processes the component tree through three main phases:
2376    /// 1. **Measure Phase**: Calculate sizes and positions for all components
2377    /// 2. **Graph Generation**: Extract render fragments from component
2378    ///    metadata
2379    /// 3. **State Handling**: Process user interactions and events
2380    ///
2381    /// Returns a tuple of (graph, window_requests) where the graph contains
2382    /// the render ops for the current frame.
2383    #[tracing::instrument(level = "debug", skip(self, params))]
2384    pub(crate) fn compute(
2385        &mut self,
2386        params: ComputeParams,
2387        mode: ComputeMode,
2388    ) -> (
2389        RenderGraph,
2390        WindowRequests,
2391        LayoutFrameDiagnostics,
2392        std::time::Duration,
2393        Option<FocusDirection>,
2394        bool,
2395        bool,
2396    ) {
2397        let mut stage_timer =
2398            tracing::enabled!(target: "tessera_ui::frame_cost", tracing::Level::DEBUG)
2399                .then(Instant::now);
2400        self.metadatas.geometry_visits = 0;
2401        self.metadatas.geometry_changed.clear();
2402        self.metadatas.size_changed.clear();
2403        self.metadatas.placement_order_changed.clear();
2404        self.metadatas.paint_changed.clear();
2405        self.focus_owner.begin_layout();
2406        let ComputeParams {
2407            screen_size,
2408            mut cursor_position,
2409            mut pointer_changes,
2410            keyboard_events,
2411            ime_events,
2412            retry_focus_move,
2413            retry_focus_reveal,
2414            modifiers,
2415            layout_dirty_nodes,
2416        } = params;
2417        let Some(root_node) = self
2418            .tree
2419            .get_node_id_at(NonZero::new(1).expect("root node index must be non-zero"))
2420        else {
2421            return (
2422                RenderGraph::default(),
2423                WindowRequests::default(),
2424                LayoutFrameDiagnostics {
2425                    pointer_nodes_dispatched: self.pointer_work.1.len() as u64,
2426                    ..Default::default()
2427                },
2428                std::time::Duration::ZERO,
2429                None,
2430                false,
2431                false,
2432            );
2433        };
2434        let screen_constraint = Constraint::exact(screen_size.width, screen_size.height);
2435        for key in &layout_dirty_nodes.transform_nodes {
2436            if let Some(&node_id) = self.node_ids_by_instance_key.get(key) {
2437                self.metadatas.mark_geometry_dirty(node_id);
2438            }
2439        }
2440        for key in &layout_dirty_nodes.paint_nodes {
2441            if let Some(&node_id) = self.node_ids_by_instance_key.get(key) {
2442                self.metadatas.mark_paint_dirty(node_id);
2443            }
2444        }
2445        let StructureReconcileResult {
2446            changed_nodes: structural_dirty_nodes,
2447            removed_nodes,
2448        } = crate::runtime::take_layout_structure_changes();
2449        self.remove_layout_snapshots(&removed_nodes);
2450        // Layout nodes dropped from the tree must also drop the state-read
2451        // dependencies they registered during measurement, otherwise recycled
2452        // instance keys would keep stale placement dirtiness alive.
2453        crate::runtime::remove_layout_state_read_dependencies(&removed_nodes);
2454
2455        let mut measure_self_nodes = layout_dirty_nodes.measure_self_nodes;
2456        for key in &layout_dirty_nodes.parent_data_nodes {
2457            if let Some(&node_id) = self.node_ids_by_instance_key.get(key) {
2458                let mut parent = self.tree.get(node_id).and_then(|node| node.parent());
2459                while let Some(parent_id) = parent {
2460                    let node = &self.tree[parent_id];
2461                    if node.get().role == NodeRole::Layout || node.parent().is_none() {
2462                        measure_self_nodes.insert(node.get().instance_key);
2463                        break;
2464                    }
2465                    parent = node.parent();
2466                }
2467            }
2468        }
2469        let dirty_nodes_param = (measure_self_nodes.len()
2470            + layout_dirty_nodes
2471                .placement_self_nodes
2472                .difference(&measure_self_nodes)
2473                .count()) as u64;
2474        let dirty_nodes_structural = structural_dirty_nodes.len() as u64;
2475        let dirty_prepare_start = Instant::now();
2476        // Remeasure structural changes so placements use the current child set.
2477        measure_self_nodes.extend(structural_dirty_nodes);
2478        let dirty_paths = collect_dirty_layout_paths(
2479            &self.tree,
2480            &self.node_ids_by_instance_key,
2481            &measure_self_nodes,
2482            &layout_dirty_nodes.placement_self_nodes,
2483        );
2484        let dirty_expand_ns = dirty_prepare_start.elapsed().as_nanos() as u64;
2485        record_frame_stage(&mut stage_timer, "layout_dirty_paths");
2486        let mut diagnostics = LayoutDiagnosticsCollector::default();
2487
2488        tracing::debug!(
2489            target: "tessera_ui::frame_cost",
2490            changed = self.changed_frame_nodes.len(),
2491            removed = self.removed_frame_nodes.len(),
2492            "frame_consumer_candidates"
2493        );
2494
2495        #[cfg(feature = "profiling")]
2496        {
2497            self.profiler_dirty_nodes
2498                .extend(self.changed_frame_nodes.iter().copied());
2499            self.profiler_dirty_nodes
2500                .extend(self.removed_frame_nodes.iter().copied());
2501            self.profiler_dirty_nodes
2502                .extend(dirty_paths.keys().copied());
2503            self.profiler_dirty_nodes.insert(root_node);
2504        }
2505        self.pointer_index.invalidate(
2506            &self.tree,
2507            &self.changed_frame_nodes,
2508            &self.removed_frame_nodes,
2509        );
2510        self.accessibility_index
2511            .invalidate(&self.changed_frame_nodes, &self.removed_frame_nodes);
2512        self.focus_owner.sync_changes(
2513            &self.tree,
2514            &self.changed_frame_nodes,
2515            &self.removed_frame_nodes,
2516        );
2517        self.focus_owner.commit_pending();
2518
2519        record_frame_stage(&mut stage_timer, "consumer_attachments");
2520        {
2521            let consumed_insets = Cell::new(crate::Insets::default());
2522            let layout_ctx = LayoutContext::new(
2523                &consumed_insets,
2524                &mut self.layout_snapshots,
2525                &dirty_paths,
2526                &mut diagnostics,
2527            );
2528
2529            let measure_timer = Instant::now();
2530            debug!("Start measuring the component tree...");
2531
2532            match measure_node(
2533                root_node,
2534                &screen_constraint,
2535                &self.tree,
2536                &mut self.metadatas,
2537                Some(&layout_ctx),
2538            ) {
2539                Ok(_root_computed_data) => {
2540                    debug!("Component tree measured in {:?}", measure_timer.elapsed());
2541                }
2542                Err(e) => {
2543                    panic!(
2544                        "Root node ({root_node:?}) measurement failed: {e:?}. Aborting draw command computation."
2545                    );
2546                }
2547            }
2548        }
2549
2550        record_frame_stage(&mut stage_timer, "measure");
2551        let mut diagnostics_snapshot = diagnostics.snapshot(
2552            dirty_nodes_param,
2553            dirty_nodes_structural,
2554            dirty_paths.len() as u64,
2555            dirty_expand_ns,
2556        );
2557        diagnostics_snapshot.geometry_nodes_updated =
2558            populate_layout_metadata(root_node, &self.tree, &mut self.metadatas);
2559        self.defer_unplaced_layout_paths(measure_self_nodes.iter().copied(), false);
2560        self.defer_unplaced_layout_paths(
2561            layout_dirty_nodes.placement_self_nodes.iter().copied(),
2562            true,
2563        );
2564        for key in &layout_dirty_nodes.transform_nodes {
2565            if let Some(id) = self.node_ids_by_instance_key.get(key)
2566                && let Some(metadata) = self.metadatas.get_mut(id)
2567                && metadata.abs_position.is_none()
2568            {
2569                metadata.geometry_initialized = false;
2570            }
2571        }
2572        record_frame_stage(&mut stage_timer, "geometry");
2573
2574        match mode {
2575            ComputeMode::Full => {}
2576            #[cfg(any(test, feature = "testing"))]
2577            ComputeMode::LayoutOnly => {
2578                self.frame_scene.clear_structure_changes();
2579                self.collect_globally_positioned_invocations(screen_size);
2580                diagnostics_snapshot.accessibility_nodes_updated =
2581                    self.sync_accessibility(root_node, true) as u64;
2582                self.focus_owner.sync_geometry_changes(
2583                    &self.tree,
2584                    &self.metadatas,
2585                    &self.metadatas.geometry_changed,
2586                    &self.changed_frame_nodes,
2587                );
2588                self.clear_frame_structure_changes();
2589                let _ = self.dispatch_pointer_input(
2590                    root_node,
2591                    pointer_changes.as_mut_slice(),
2592                    &mut cursor_position,
2593                    modifiers,
2594                );
2595                diagnostics_snapshot.geometry_nodes_visited = self.metadatas.geometry_visits;
2596                diagnostics_snapshot.pointer_nodes_updated = self.pointer_work.0;
2597                diagnostics_snapshot.pointer_nodes_dispatched = self.pointer_work.1.len() as u64;
2598                diagnostics_snapshot.pointer_hit_test_candidates =
2599                    self.pointer_index.hit_candidates.get();
2600                return (
2601                    RenderGraph::default(),
2602                    WindowRequests::default(),
2603                    diagnostics_snapshot,
2604                    std::time::Duration::ZERO,
2605                    None,
2606                    false,
2607                    false,
2608                );
2609            }
2610        };
2611
2612        let record_timer = Instant::now();
2613        let _focus_owner_guard = bind_focus_owner(&mut self.focus_owner);
2614        record_layout_commands(root_node, &self.tree, &mut self.metadatas);
2615        let record_cost = record_timer.elapsed();
2616        record_frame_stage(&mut stage_timer, "paint");
2617        diagnostics_snapshot.geometry_nodes_updated +=
2618            populate_layout_metadata(root_node, &self.tree, &mut self.metadatas);
2619        record_frame_stage(&mut stage_timer, "geometry_after_paint");
2620        tracing::debug!(
2621            target: "tessera_ui::frame_cost",
2622            geometry_journal_nodes = self.metadatas.geometry_changed.len(),
2623            "geometry_journal"
2624        );
2625        self.collect_globally_positioned_invocations(screen_size);
2626        record_frame_stage(&mut stage_timer, "position_observers");
2627        diagnostics_snapshot.accessibility_nodes_updated =
2628            self.sync_accessibility(root_node, false) as u64;
2629        record_frame_stage(&mut stage_timer, "accessibility");
2630        diagnostics_snapshot.paint_nodes_recorded = self.metadatas.paint_changed.len() as u64;
2631
2632        let compute_draw_timer = Instant::now();
2633        debug!("Start computing render graph...");
2634        let (graph, scene_updates) = self.frame_scene.update(
2635            root_node,
2636            &self.tree,
2637            &self.metadatas,
2638            &self.changed_frame_nodes,
2639            &self.removed_frame_nodes,
2640            screen_size,
2641        );
2642        diagnostics_snapshot.scene_nodes_updated = scene_updates;
2643        record_frame_stage(&mut stage_timer, "scene");
2644        self.focus_owner.sync_geometry_changes(
2645            &self.tree,
2646            &self.metadatas,
2647            &self.metadatas.geometry_changed,
2648            &self.changed_frame_nodes,
2649        );
2650        self.clear_frame_structure_changes();
2651        record_frame_stage(&mut stage_timer, "focus_geometry");
2652        debug!(
2653            "Render graph built in {:?}, total ops: {}",
2654            compute_draw_timer.elapsed(),
2655            graph.ops().len()
2656        );
2657
2658        let input_dispatch_timer = Instant::now();
2659        debug!("Start executing typed input dispatch...");
2660        let mut window_requests = self.dispatch_pointer_input(
2661            root_node,
2662            pointer_changes.as_mut_slice(),
2663            &mut cursor_position,
2664            modifiers,
2665        );
2666        record_frame_stage(&mut stage_timer, "pointer");
2667
2668        diagnostics_snapshot.geometry_nodes_visited = self.metadatas.geometry_visits;
2669        diagnostics_snapshot.pointer_nodes_updated = self.pointer_work.0;
2670        diagnostics_snapshot.pointer_nodes_dispatched = self.pointer_work.1.len() as u64;
2671        diagnostics_snapshot.pointer_hit_test_candidates = self.pointer_index.hit_candidates.get();
2672        self.focus_owner.commit_pending();
2673        let requested_move = self
2674            .focus_owner
2675            .take_pending_navigation()
2676            .or(retry_focus_move);
2677        let pending_focus_move_retry = requested_move.and_then(|direction| {
2678            match try_dispatch_focus_move_request(&self.tree, direction, &mut self.focus_owner) {
2679                FocusMoveRequestResult::Retry(direction) => Some(direction),
2680                FocusMoveRequestResult::Moved | FocusMoveRequestResult::NotHandled => None,
2681            }
2682        });
2683        self.queue_focused_input(keyboard_events, ime_events);
2684        let pending_focus_move_retry = pending_focus_move_retry
2685            .or_else(|| self.dispatch_pending_focused_input(root_node, &mut window_requests));
2686        self.focus_owner
2687            .retain_navigation_retry(pending_focus_move_retry);
2688        let pending_keyboard_retry =
2689            needs_deferred_focus_retry(&self.focus_owner, !self.pending_focused_input.is_empty());
2690        let pending_focus_reveal_retry =
2691            if pending_focus_move_retry.is_none() && !self.focus_owner.keyboard_is_deferred() {
2692                dispatch_pending_focus_reveal_request(
2693                    &self.tree,
2694                    &self.metadatas,
2695                    &mut self.focus_owner,
2696                    retry_focus_reveal,
2697                )
2698            } else {
2699                false
2700            };
2701
2702        debug!(
2703            "Typed input dispatch executed in {:?}",
2704            input_dispatch_timer.elapsed()
2705        );
2706        record_frame_stage(&mut stage_timer, "focused_input");
2707        (
2708            graph,
2709            window_requests,
2710            diagnostics_snapshot,
2711            record_cost,
2712            pending_focus_move_retry,
2713            pending_focus_reveal_retry,
2714            pending_keyboard_retry,
2715        )
2716    }
2717}
2718
2719fn drain_pending_focused_input<E>(
2720    queue: &mut VecDeque<E>,
2721    mut dispatch: impl FnMut(E) -> (Option<FocusDirection>, bool),
2722) -> Option<FocusDirection> {
2723    while let Some(event) = queue.pop_front() {
2724        let (retry, defer) = dispatch(event);
2725        if let Some(direction) = retry {
2726            return Some(direction);
2727        }
2728        if defer {
2729            return None;
2730        }
2731    }
2732    None
2733}
2734
2735#[derive(Clone)]
2736struct NodeInputContext {
2737    local_to_window: crate::LocalToWindow,
2738    input_transforms: Option<node::InputTransforms>,
2739    event_clip_bounds: Option<crate::FloatRect>,
2740    base_abs_pos: PxPosition,
2741    abs_pos: PxPosition,
2742    event_clip_rect: Option<PxRect>,
2743    node_computed_data: ComputedData,
2744    instance_logic_id: u64,
2745    instance_key: u64,
2746    fn_name: &'static str,
2747    parent_id: Option<indextree::NodeId>,
2748}
2749
2750fn resolve_node_input_context(
2751    tree: &ComponentNodeTree,
2752    metadatas: &ComponentNodeMetaDatas,
2753    node_id: indextree::NodeId,
2754) -> Option<NodeInputContext> {
2755    let Some(node_ref) = tree.get(node_id) else {
2756        warn!("Node not found for node {node_id:?}; skipping input handling");
2757        return None;
2758    };
2759    let node = node_ref.get();
2760    if node.role != NodeRole::Layout {
2761        return None;
2762    }
2763
2764    let Some(metadata) = metadatas.get(&node_id) else {
2765        warn!("Input metadata missing for node {node_id:?}; skipping input handling");
2766        return None;
2767    };
2768    let Some(base_abs_pos) = metadata.base_abs_position else {
2769        warn!("Base absolute position missing for node {node_id:?}; skipping input handling");
2770        return None;
2771    };
2772    let Some(abs_pos) = metadata.abs_position else {
2773        warn!("Absolute position missing for node {node_id:?}; skipping input handling");
2774        return None;
2775    };
2776    let event_clip_rect = metadata.event_clip_rect();
2777    let Some(node_computed_data) = metadata.computed_data else {
2778        warn!(
2779            "Computed data not found for node {:?} during input dispatch.",
2780            node_id
2781        );
2782        return None;
2783    };
2784    let instance_logic_id = node.instance_logic_id;
2785    let instance_key = node.instance_key;
2786    let parent_id = node_ref.parent();
2787
2788    Some(NodeInputContext {
2789        local_to_window: metadata
2790            .local_to_window
2791            .unwrap_or_else(|| crate::LocalToWindow::new(1.0, abs_pos).unwrap()),
2792        input_transforms: metadata.input_transforms.as_deref().cloned(),
2793        event_clip_bounds: metadata.event_clip_bounds,
2794        base_abs_pos,
2795        abs_pos,
2796        event_clip_rect,
2797        node_computed_data,
2798        instance_logic_id,
2799        instance_key,
2800        fn_name: node.fn_name,
2801        parent_id,
2802    })
2803}
2804
2805fn attach_ime_position_if_needed(
2806    window_requests: &mut WindowRequests,
2807    transform: crate::LocalToWindow,
2808    source: Option<crate::focus::FocusHandleId>,
2809    active: Option<crate::focus::FocusHandleId>,
2810) {
2811    let Some(request) = window_requests.ime_request.as_mut() else {
2812        return;
2813    };
2814    let source = if request.position.is_none() {
2815        source
2816    } else {
2817        request.client_id
2818    };
2819    if source.is_none() || source != active {
2820        window_requests.ime_request = None;
2821        return;
2822    }
2823    if request.position.is_none() {
2824        request.client_id = source;
2825        request.position = Some(transform.map_point(request.local_position));
2826        let size = transform.map_size(request.size);
2827        request.size = PxSize::new(
2828            crate::Px(size[0].ceil() as i32),
2829            crate::Px(size[1].ceil() as i32),
2830        );
2831    }
2832}
2833
2834fn inside_shape_clips(
2835    tree: &ComponentNodeTree,
2836    metadatas: &ComponentNodeMetaDatas,
2837    node_id: indextree::NodeId,
2838    position: PxPosition,
2839) -> bool {
2840    if !metadatas
2841        .get(&node_id)
2842        .is_some_and(|metadata| metadata.has_shape_clipping)
2843    {
2844        return true;
2845    }
2846    node_id.ancestors(tree).all(|ancestor| {
2847        let Some(metadata) = metadatas.get(&ancestor) else {
2848            return true;
2849        };
2850        let (Some(&region), Some(origin)) = (metadata.clip_region(), metadata.abs_position) else {
2851            return true;
2852        };
2853        let transform = metadata
2854            .local_to_window
2855            .unwrap_or_else(|| crate::LocalToWindow::new(1.0, origin).unwrap());
2856        map_clip_region(region, transform).contains(position)
2857    })
2858}
2859
2860fn bounds_hit_path(
2861    node_id: indextree::NodeId,
2862    tree: &ComponentNodeTree,
2863    metadatas: &ComponentNodeMetaDatas,
2864    position: PxPosition,
2865) -> Option<indextree::NodeId> {
2866    let node = tree.get(node_id)?;
2867    if node.get().role != NodeRole::Layout {
2868        return None;
2869    }
2870    let metadata = metadatas.get(&node_id)?;
2871    let base_abs_pos = metadata.base_abs_position?;
2872    let abs_pos = metadata.abs_position?;
2873    let size = metadata.computed_data?;
2874    if size.width.0 <= 0 || size.height.0 <= 0 {
2875        return None;
2876    }
2877    if metadata
2878        .event_clip_bounds
2879        .is_some_and(|bounds| !bounds.contains(position))
2880    {
2881        return None;
2882    }
2883    if let Some(clip_rect) = metadata.event_clip_rect()
2884        && !clip_rect.contains(position)
2885    {
2886        return None;
2887    }
2888    if !inside_shape_clips(tree, metadatas, node_id, position) {
2889        return None;
2890    }
2891    let transform = metadata
2892        .local_to_window
2893        .unwrap_or_else(|| crate::LocalToWindow::new(1.0, abs_pos).unwrap());
2894    let bounds = transform.map_rect(crate::FloatRect::from_position_size(
2895        PxPosition::ZERO,
2896        PxSize::new(size.width, size.height),
2897    ));
2898    let node_data = node.get();
2899    let has_pointer_modifier = node_data.modifier.ordered_actions().iter().any(|action| {
2900        matches!(
2901            action,
2902            OrderedModifierAction::Cursor(_)
2903                | OrderedModifierAction::PointerPreviewInput(_)
2904                | OrderedModifierAction::PointerInput(_)
2905                | OrderedModifierAction::PointerFinalInput(_)
2906        )
2907    });
2908    let has_pointer_handler = node_data.interaction().has_pointer_handlers();
2909    if !has_pointer_modifier && !has_pointer_handler {
2910        return None;
2911    }
2912    let node_handles_hover = has_pointer_modifier
2913        && node_handles_pointer_at_position(
2914            node_data,
2915            base_abs_pos,
2916            size,
2917            position,
2918            transform.scale(),
2919            metadata.input_transforms.as_deref(),
2920        )
2921        || bounds.contains(position) && has_pointer_handler;
2922    node_handles_hover.then_some(node_id)
2923}
2924
2925fn node_handles_pointer_at_position(
2926    node: &crate::component_tree::ComponentNode,
2927    base_abs_pos: PxPosition,
2928    size: ComputedData,
2929    position: PxPosition,
2930    scale: f32,
2931    transforms: Option<&node::InputTransforms>,
2932) -> bool {
2933    let mut current_abs_pos = base_abs_pos;
2934    let size = PxSize::new(size.width, size.height);
2935    for (index, action) in node.modifier.ordered_actions().into_iter().enumerate() {
2936        if let Some(transform) = transforms.and_then(|transforms| transforms.get(index)) {
2937            current_abs_pos = transform.translation();
2938        }
2939        match action {
2940            OrderedModifierAction::Placement(placement) => {
2941                if transforms.is_none() {
2942                    crate::work::record(
2943                        node.instance_logic_id,
2944                        crate::WorkPhase::Transform,
2945                        placement.node().diagnostic_name(),
2946                    );
2947                    current_abs_pos = placement.node().transform_position(current_abs_pos);
2948                }
2949            }
2950            OrderedModifierAction::Cursor(_)
2951            | OrderedModifierAction::PointerPreviewInput(_)
2952            | OrderedModifierAction::PointerInput(_)
2953            | OrderedModifierAction::PointerFinalInput(_) => {
2954                let bounds = crate::LocalToWindow::new(scale, current_abs_pos)
2955                    .unwrap()
2956                    .map_rect(crate::FloatRect::from_position_size(PxPosition::ZERO, size));
2957                if bounds.contains(position) {
2958                    return true;
2959                }
2960            }
2961            _ => {}
2962        }
2963    }
2964    false
2965}
2966
2967fn resolve_node_hover_cursor_icon(
2968    node: &crate::component_tree::ComponentNode,
2969    base_abs_pos: PxPosition,
2970    size: ComputedData,
2971    position: PxPosition,
2972    scale: f32,
2973    transforms: Option<&node::InputTransforms>,
2974) -> Option<winit::window::CursorIcon> {
2975    let mut current_abs_pos = base_abs_pos;
2976    let size = PxSize::new(size.width, size.height);
2977    let mut resolved = None;
2978    for (index, action) in node.modifier.ordered_actions().into_iter().enumerate() {
2979        if let Some(transform) = transforms.and_then(|transforms| transforms.get(index)) {
2980            current_abs_pos = transform.translation();
2981        }
2982        match action {
2983            OrderedModifierAction::Placement(placement) => {
2984                if transforms.is_none() {
2985                    crate::work::record(
2986                        node.instance_logic_id,
2987                        crate::WorkPhase::Transform,
2988                        placement.node().diagnostic_name(),
2989                    );
2990                    current_abs_pos = placement.node().transform_position(current_abs_pos);
2991                }
2992            }
2993            OrderedModifierAction::Cursor(cursor) => {
2994                let bounds = crate::LocalToWindow::new(scale, current_abs_pos)
2995                    .unwrap()
2996                    .map_rect(crate::FloatRect::from_position_size(PxPosition::ZERO, size));
2997                if bounds.contains(position) {
2998                    resolved = Some(cursor.cursor_icon());
2999                }
3000            }
3001            _ => {}
3002        }
3003    }
3004    resolved
3005}
3006
3007fn build_pointer_change_paths(
3008    index: &pointer::PointerIndex,
3009    tree: &ComponentNodeTree,
3010    metadatas: &ComponentNodeMetaDatas,
3011    pointer_changes: &[PointerChange],
3012    cursor_position: Option<PxPosition>,
3013    active_pointer_paths: &mut HashMap<u64, Vec<u64>>,
3014) -> Vec<Vec<u64>> {
3015    let mut paths = Vec::with_capacity(pointer_changes.len());
3016    for change in pointer_changes {
3017        let debug_position = change.resolved_position(cursor_position);
3018        let path = match &change.content {
3019            CursorEventContent::Pressed(_) => {
3020                let computed_path = hit_path_instance_keys(index, tree, metadatas, debug_position);
3021                if computed_path.is_empty() {
3022                    active_pointer_paths.remove(&change.pointer_id);
3023                } else {
3024                    active_pointer_paths.insert(change.pointer_id, computed_path.clone());
3025                }
3026                computed_path
3027            }
3028            CursorEventContent::Moved(position) => active_pointer_paths
3029                .get(&change.pointer_id)
3030                .cloned()
3031                .unwrap_or_else(|| hit_path_instance_keys(index, tree, metadatas, Some(*position))),
3032            CursorEventContent::Released(_) | CursorEventContent::Cancelled(_) => {
3033                let computed = active_pointer_paths
3034                    .get(&change.pointer_id)
3035                    .cloned()
3036                    .unwrap_or_else(|| {
3037                        hit_path_instance_keys(index, tree, metadatas, debug_position)
3038                    });
3039                active_pointer_paths.remove(&change.pointer_id);
3040                computed
3041            }
3042            CursorEventContent::Scroll(_) => active_pointer_paths
3043                .get(&change.pointer_id)
3044                .cloned()
3045                .unwrap_or_else(|| hit_path_instance_keys(index, tree, metadatas, debug_position)),
3046        };
3047        paths.push(path);
3048    }
3049    paths
3050}
3051
3052fn hit_path_instance_keys(
3053    index: &pointer::PointerIndex,
3054    tree: &ComponentNodeTree,
3055    metadatas: &ComponentNodeMetaDatas,
3056    position: Option<PxPosition>,
3057) -> Vec<u64> {
3058    index
3059        .hit_path(tree, metadatas, position)
3060        .into_iter()
3061        .filter_map(|node_id| tree.get(node_id).map(|node| node.get().instance_key))
3062        .collect()
3063}
3064
3065fn collect_focus_chain_node_ids(
3066    root_node: indextree::NodeId,
3067    tree: &ComponentNodeTree,
3068    focus_owner: &FocusOwner,
3069) -> Vec<indextree::NodeId> {
3070    fn live_node(
3071        tree: &ComponentNodeTree,
3072        node_id: indextree::NodeId,
3073    ) -> Option<&indextree::Node<ComponentNode>> {
3074        tree.get(node_id).filter(|node| !node.is_removed())
3075    }
3076
3077    let Some(focused_node_id) = focus_owner.active_component_node_id() else {
3078        return Vec::new();
3079    };
3080    if live_node(tree, focused_node_id).is_none() {
3081        return Vec::new();
3082    }
3083
3084    let mut chain = Vec::new();
3085    let mut current = Some(focused_node_id);
3086    while let Some(node_id) = current {
3087        chain.push(node_id);
3088        if node_id == root_node {
3089            break;
3090        }
3091        current = live_node(tree, node_id).and_then(|node| node.parent());
3092    }
3093    if chain.last().copied() != Some(root_node) {
3094        return Vec::new();
3095    }
3096    chain.reverse();
3097    chain
3098}
3099
3100#[derive(Clone)]
3101struct PointerNodePlan {
3102    context: NodeInputContext,
3103    actions: Vec<OrderedModifierAction>,
3104}
3105
3106struct PointerDispatchPlan {
3107    preorder: Vec<indextree::NodeId>,
3108    postorder: Vec<indextree::NodeId>,
3109    nodes: HashMap<indextree::NodeId, Arc<PointerNodePlan>>,
3110}
3111
3112struct PointerInputDispatchContext<'a> {
3113    tree: &'a ComponentNodeTree,
3114    metadatas: &'a ComponentNodeMetaDatas,
3115    cursor_position: &'a mut Option<PxPosition>,
3116    pointer_changes: &'a mut [PointerChange],
3117    pointer_change_paths: &'a [Vec<u64>],
3118    nodes: &'a HashMap<indextree::NodeId, Arc<PointerNodePlan>>,
3119    active_pointer_paths: &'a HashMap<u64, Vec<u64>>,
3120    active_pointer_positions: &'a HashMap<u64, Option<PxPosition>>,
3121    modifiers: winit::keyboard::ModifiersState,
3122    window_requests: &'a mut WindowRequests,
3123    focus_owner: &'a mut FocusOwner,
3124}
3125
3126fn dispatch_pointer_modifiers_for_node_pass(
3127    dispatch_ctx: &mut PointerInputDispatchContext<'_>,
3128    node_id: indextree::NodeId,
3129    pass: PointerEventPass,
3130) {
3131    let nodes = dispatch_ctx.nodes;
3132    let Some(plan) = nodes.get(&node_id) else {
3133        return;
3134    };
3135    let mut current_abs_pos = plan.context.base_abs_pos;
3136    for (index, action) in plan.actions.iter().enumerate() {
3137        if let Some(transform) = plan
3138            .context
3139            .input_transforms
3140            .as_ref()
3141            .and_then(|transforms| transforms.get(index))
3142        {
3143            current_abs_pos = transform.translation();
3144        }
3145        match action {
3146            OrderedModifierAction::Placement(placement) => {
3147                if plan.context.input_transforms.is_none() {
3148                    crate::work::record(
3149                        plan.context.instance_logic_id,
3150                        crate::WorkPhase::Transform,
3151                        placement.node().diagnostic_name(),
3152                    );
3153                    current_abs_pos = placement.node().transform_position(current_abs_pos);
3154                }
3155            }
3156            OrderedModifierAction::PointerPreviewInput(modifier)
3157                if pass == PointerEventPass::Initial =>
3158            {
3159                run_pointer_modifier_for_node(
3160                    dispatch_ctx,
3161                    node_id,
3162                    pass,
3163                    current_abs_pos,
3164                    modifier.as_ref(),
3165                );
3166            }
3167            OrderedModifierAction::PointerInput(modifier) if pass == PointerEventPass::Main => {
3168                run_pointer_modifier_for_node(
3169                    dispatch_ctx,
3170                    node_id,
3171                    pass,
3172                    current_abs_pos,
3173                    modifier.as_ref(),
3174                );
3175            }
3176            OrderedModifierAction::PointerFinalInput(modifier)
3177                if pass == PointerEventPass::Final =>
3178            {
3179                run_pointer_modifier_for_node(
3180                    dispatch_ctx,
3181                    node_id,
3182                    pass,
3183                    current_abs_pos,
3184                    modifier.as_ref(),
3185                );
3186            }
3187            _ => {}
3188        }
3189    }
3190}
3191
3192fn run_pointer_handler_for_node(
3193    dispatch_ctx: &mut PointerInputDispatchContext<'_>,
3194    node_id: indextree::NodeId,
3195    pass: PointerEventPass,
3196    pointer_handler: &PointerInputHandlerFn,
3197) {
3198    let Some(abs_pos) = dispatch_ctx
3199        .nodes
3200        .get(&node_id)
3201        .map(|node| node.context.abs_pos)
3202    else {
3203        return;
3204    };
3205    run_pointer_input_for_node(dispatch_ctx, node_id, pass, abs_pos, pointer_handler);
3206}
3207
3208fn run_pointer_modifier_for_node(
3209    dispatch_ctx: &mut PointerInputDispatchContext<'_>,
3210    node_id: indextree::NodeId,
3211    pass: PointerEventPass,
3212    abs_pos_override: PxPosition,
3213    pointer_modifier: &dyn PointerInputModifierNode,
3214) {
3215    run_pointer_input_for_node(dispatch_ctx, node_id, pass, abs_pos_override, |input| {
3216        pointer_modifier.on_pointer_input(input)
3217    });
3218}
3219
3220fn run_pointer_input_for_node<F>(
3221    dispatch_ctx: &mut PointerInputDispatchContext<'_>,
3222    node_id: indextree::NodeId,
3223    pass: PointerEventPass,
3224    abs_pos_override: PxPosition,
3225    dispatch: F,
3226) where
3227    F: FnOnce(PointerInput<'_>),
3228{
3229    let nodes = dispatch_ctx.nodes;
3230    let Some(NodeInputContext {
3231        local_to_window,
3232        input_transforms: _,
3233        event_clip_bounds,
3234        base_abs_pos: _,
3235        abs_pos: _,
3236        event_clip_rect,
3237        node_computed_data,
3238        instance_logic_id,
3239        instance_key,
3240        fn_name,
3241        parent_id,
3242    }) = nodes.get(&node_id).map(|node| &node.context)
3243    else {
3244        return;
3245    };
3246    let event_clip_rect = *event_clip_rect;
3247    let node_computed_data = *node_computed_data;
3248    let instance_logic_id = *instance_logic_id;
3249    let instance_key = *instance_key;
3250    let parent_id = *parent_id;
3251    #[cfg(not(feature = "profiling"))]
3252    let _ = parent_id;
3253
3254    let abs_pos = abs_pos_override;
3255    let transform = crate::LocalToWindow::new(local_to_window.scale(), abs_pos).unwrap();
3256
3257    let owns_sample = dispatch_ctx
3258        .pointer_change_paths
3259        .iter()
3260        .any(|path| path.contains(&instance_key));
3261    let captured_position = if owns_sample {
3262        None
3263    } else {
3264        dispatch_ctx
3265            .active_pointer_paths
3266            .iter()
3267            .filter_map(|(&pointer_id, path)| {
3268                if pointer_id == MOUSE_POINTER_ID || !path.contains(&instance_key) {
3269                    return None;
3270                }
3271                dispatch_ctx
3272                    .active_pointer_positions
3273                    .get(&pointer_id)
3274                    .map(|position| (pointer_id, *position))
3275            })
3276            .min_by_key(|(pointer_id, _)| *pointer_id)
3277            .map(|(_, position)| position)
3278    };
3279    let mut captured_cursor_position = captured_position.flatten();
3280    let mut cursor_position_ref = if captured_position.is_some() {
3281        &mut captured_cursor_position
3282    } else {
3283        &mut *dispatch_ctx.cursor_position
3284    };
3285    let mut dummy_cursor_position = None;
3286    if let (Some(cursor_pos), Some(clip_rect)) = (*cursor_position_ref, event_clip_rect)
3287        && !clip_rect.contains(cursor_pos)
3288    {
3289        cursor_position_ref = &mut dummy_cursor_position;
3290    }
3291    if let Some(position) = *cursor_position_ref
3292        && !inside_shape_clips(dispatch_ctx.tree, dispatch_ctx.metadatas, node_id, position)
3293    {
3294        cursor_position_ref = &mut dummy_cursor_position;
3295    }
3296    if let (Some(position), Some(bounds)) = (*cursor_position_ref, *event_clip_bounds) {
3297        if !bounds.contains(position) {
3298            cursor_position_ref = &mut dummy_cursor_position;
3299        }
3300    }
3301    let current_cursor_position = cursor_position_ref.map(|pos| transform.inverse_point(pos));
3302    let mut selected_change_indices = Vec::new();
3303    let mut local_pointer_changes = Vec::new();
3304    for (index, change) in dispatch_ctx.pointer_changes.iter().enumerate() {
3305        if change.is_consumed() {
3306            continue;
3307        }
3308        let Some(path) = dispatch_ctx.pointer_change_paths.get(index) else {
3309            continue;
3310        };
3311        if path.contains(&instance_key) {
3312            selected_change_indices.push(index);
3313            let mut local_change = change.clone();
3314            if transform.scale() != 1.0
3315                && let CursorEventContent::Scroll(scroll) = &mut local_change.content
3316            {
3317                scroll.normalize_to_pixels();
3318                scroll.delta_x /= transform.scale();
3319                scroll.delta_y /= transform.scale();
3320            }
3321            local_pointer_changes.push(local_change);
3322        }
3323    }
3324    #[cfg(feature = "profiling")]
3325    let mut profiler_guard = Some(ProfilerScopeGuard::new(
3326        ProfilerPhase::Input,
3327        Some(node_id),
3328        parent_id,
3329        Some(fn_name),
3330    ));
3331    let replay_boundary_instance_key =
3332        nearest_replay_boundary_instance_key(node_id, dispatch_ctx.tree);
3333    let _node_ctx_guard =
3334        push_current_node_with_instance_logic_id(node_id, instance_logic_id, fn_name);
3335    let _instance_ctx_guard = push_current_component_instance_key(replay_boundary_instance_key);
3336    let _phase_guard = push_phase(RuntimePhase::Input);
3337    let _focus_owner_guard = bind_focus_owner(dispatch_ctx.focus_owner);
3338    let input = PointerInput {
3339        pass,
3340        local_to_window: transform,
3341        computed_data: node_computed_data,
3342        cursor_position_rel: current_cursor_position,
3343        cursor_position_abs: cursor_position_ref,
3344        pointer_changes: &mut local_pointer_changes,
3345        key_modifiers: dispatch_ctx.modifiers,
3346        ime_request: &mut dispatch_ctx.window_requests.ime_request,
3347        request_window_drag: &mut dispatch_ctx.window_requests.request_window_drag,
3348    };
3349    crate::runtime::with_pointer_state_reader(instance_key, || dispatch(input));
3350    for (local_change, &original_index) in local_pointer_changes
3351        .iter()
3352        .zip(selected_change_indices.iter())
3353    {
3354        if let Some(original) = dispatch_ctx.pointer_changes.get_mut(original_index) {
3355            let mut window_change = local_change.clone();
3356            if let CursorEventContent::Scroll(scroll) = &mut window_change.content {
3357                scroll.delta_x *= transform.scale();
3358                scroll.delta_y *= transform.scale();
3359            }
3360            *original = window_change;
3361        }
3362    }
3363
3364    #[cfg(feature = "profiling")]
3365    {
3366        let abs_tuple = (abs_pos.x_px().0, abs_pos.y_px().0);
3367        if let Some(g) = &mut profiler_guard {
3368            g.set_positions(Some(abs_tuple));
3369        }
3370        let _ = profiler_guard.take();
3371    }
3372    let ime_client = dispatch_ctx
3373        .window_requests
3374        .ime_request
3375        .as_ref()
3376        .filter(|request| request.position.is_none())
3377        .and_then(|_| dispatch_ctx.focus_owner.active_component_node_id())
3378        .filter(|focused| {
3379            focused
3380                .ancestors(dispatch_ctx.tree)
3381                .any(|owner| owner == node_id)
3382                || node_id
3383                    .ancestors(dispatch_ctx.tree)
3384                    .any(|owner| owner == *focused)
3385        })
3386        .and(dispatch_ctx.focus_owner.active_handle_id());
3387    attach_ime_position_if_needed(
3388        dispatch_ctx.window_requests,
3389        transform,
3390        ime_client,
3391        dispatch_ctx.focus_owner.active_handle_id(),
3392    );
3393}
3394
3395struct KeyboardInputDispatchContext<'a> {
3396    tree: &'a ComponentNodeTree,
3397    metadatas: &'a ComponentNodeMetaDatas,
3398    keyboard_events: &'a mut Vec<crate::KeyboardEvent>,
3399    modifiers: winit::keyboard::ModifiersState,
3400    ime_client: Option<FocusHandleId>,
3401    window_requests: &'a mut WindowRequests,
3402    focus_owner: &'a mut FocusOwner,
3403}
3404
3405fn run_keyboard_input_for_node<F>(
3406    dispatch_ctx: &mut KeyboardInputDispatchContext<'_>,
3407    node_id: indextree::NodeId,
3408    dispatch: F,
3409) where
3410    F: FnOnce(KeyboardInput<'_>),
3411{
3412    let Some(NodeInputContext {
3413        local_to_window: transform,
3414        abs_pos,
3415        event_clip_rect: _,
3416        node_computed_data,
3417        instance_logic_id,
3418        instance_key: _,
3419        fn_name,
3420        parent_id,
3421        ..
3422    }) = resolve_node_input_context(dispatch_ctx.tree, dispatch_ctx.metadatas, node_id)
3423    else {
3424        return;
3425    };
3426    #[cfg(not(feature = "profiling"))]
3427    let _ = (parent_id, abs_pos);
3428
3429    #[cfg(feature = "profiling")]
3430    let mut profiler_guard = Some(ProfilerScopeGuard::new(
3431        ProfilerPhase::Input,
3432        Some(node_id),
3433        parent_id,
3434        Some(fn_name),
3435    ));
3436    let replay_boundary_instance_key =
3437        nearest_replay_boundary_instance_key(node_id, dispatch_ctx.tree);
3438    let _node_ctx_guard =
3439        push_current_node_with_instance_logic_id(node_id, instance_logic_id, fn_name);
3440    let _instance_ctx_guard = push_current_component_instance_key(replay_boundary_instance_key);
3441    let _phase_guard = push_phase(RuntimePhase::Input);
3442    let _focus_owner_guard = bind_focus_owner(dispatch_ctx.focus_owner);
3443    let input = KeyboardInput {
3444        computed_data: node_computed_data,
3445        keyboard_events: dispatch_ctx.keyboard_events,
3446        key_modifiers: dispatch_ctx.modifiers,
3447        ime_request: &mut dispatch_ctx.window_requests.ime_request,
3448    };
3449    dispatch(input);
3450
3451    #[cfg(feature = "profiling")]
3452    {
3453        let abs_tuple = (abs_pos.x_px().0, abs_pos.y_px().0);
3454        if let Some(g) = &mut profiler_guard {
3455            g.set_positions(Some(abs_tuple));
3456        }
3457        let _ = profiler_guard.take();
3458    }
3459    attach_ime_position_if_needed(
3460        dispatch_ctx.window_requests,
3461        transform,
3462        dispatch_ctx.ime_client,
3463        dispatch_ctx.focus_owner.active_handle_id(),
3464    );
3465}
3466
3467fn run_ime_input_for_node<F>(
3468    tree: &ComponentNodeTree,
3469    metadatas: &ComponentNodeMetaDatas,
3470    node_id: indextree::NodeId,
3471    ime_events: &mut Vec<winit::event::Ime>,
3472    window_requests: &mut WindowRequests,
3473    focus_owner: &mut FocusOwner,
3474    ime_client: Option<FocusHandleId>,
3475    dispatch: F,
3476) where
3477    F: FnOnce(ImeInput<'_>),
3478{
3479    let Some(NodeInputContext {
3480        local_to_window: transform,
3481        abs_pos,
3482        event_clip_rect: _,
3483        node_computed_data,
3484        instance_logic_id,
3485        instance_key: _,
3486        fn_name,
3487        parent_id,
3488        ..
3489    }) = resolve_node_input_context(tree, metadatas, node_id)
3490    else {
3491        return;
3492    };
3493    #[cfg(not(feature = "profiling"))]
3494    let _ = (parent_id, abs_pos);
3495
3496    #[cfg(feature = "profiling")]
3497    let mut profiler_guard = Some(ProfilerScopeGuard::new(
3498        ProfilerPhase::Input,
3499        Some(node_id),
3500        parent_id,
3501        Some(fn_name),
3502    ));
3503    let replay_boundary_instance_key = nearest_replay_boundary_instance_key(node_id, tree);
3504    let _node_ctx_guard =
3505        push_current_node_with_instance_logic_id(node_id, instance_logic_id, fn_name);
3506    let _instance_ctx_guard = push_current_component_instance_key(replay_boundary_instance_key);
3507    let _phase_guard = push_phase(RuntimePhase::Input);
3508    let _focus_owner_guard = bind_focus_owner(focus_owner);
3509    let input = ImeInput {
3510        computed_data: node_computed_data,
3511        ime_events,
3512        ime_request: &mut window_requests.ime_request,
3513    };
3514    dispatch(input);
3515
3516    #[cfg(feature = "profiling")]
3517    {
3518        let abs_tuple = (abs_pos.x_px().0, abs_pos.y_px().0);
3519        if let Some(g) = &mut profiler_guard {
3520            g.set_positions(Some(abs_tuple));
3521        }
3522        let _ = profiler_guard.take();
3523    }
3524    attach_ime_position_if_needed(
3525        window_requests,
3526        transform,
3527        ime_client,
3528        focus_owner.active_handle_id(),
3529    );
3530}
3531
3532fn dispatch_default_focus_keyboard_navigation(
3533    tree: &ComponentNodeTree,
3534    keyboard_events: &mut Vec<crate::KeyboardEvent>,
3535    modifiers: winit::keyboard::ModifiersState,
3536    focus_owner: &mut FocusOwner,
3537) -> Option<FocusDirection> {
3538    if keyboard_events.is_empty() {
3539        return None;
3540    }
3541
3542    let mut pending_focus_move_retry = None;
3543    keyboard_events.retain(|event| {
3544        if pending_focus_move_retry.is_some() {
3545            return false;
3546        }
3547        let Some(direction) = default_focus_navigation_direction(event, modifiers) else {
3548            return true;
3549        };
3550        match try_dispatch_focus_move_request(tree, direction, focus_owner) {
3551            FocusMoveRequestResult::Moved => false,
3552            FocusMoveRequestResult::Retry(direction) => {
3553                pending_focus_move_retry = Some(direction);
3554                false
3555            }
3556            FocusMoveRequestResult::NotHandled => true,
3557        }
3558    });
3559    pending_focus_move_retry
3560}
3561
3562fn default_focus_navigation_direction(
3563    event: &crate::KeyboardEvent,
3564    modifiers: winit::keyboard::ModifiersState,
3565) -> Option<FocusDirection> {
3566    if event.state != winit::event::ElementState::Pressed {
3567        return None;
3568    }
3569
3570    if modifiers.control_key() || modifiers.alt_key() || modifiers.super_key() {
3571        return None;
3572    }
3573
3574    match &event.logical_key {
3575        winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab) => {
3576            if modifiers.shift_key() {
3577                Some(FocusDirection::Previous)
3578            } else {
3579                Some(FocusDirection::Next)
3580            }
3581        }
3582        winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowLeft) => {
3583            (!modifiers.shift_key()).then_some(FocusDirection::Left)
3584        }
3585        winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowRight) => {
3586            (!modifiers.shift_key()).then_some(FocusDirection::Right)
3587        }
3588        winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowUp) => {
3589            (!modifiers.shift_key()).then_some(FocusDirection::Up)
3590        }
3591        winit::keyboard::Key::Named(winit::keyboard::NamedKey::ArrowDown) => {
3592            (!modifiers.shift_key()).then_some(FocusDirection::Down)
3593        }
3594        _ => None,
3595    }
3596}
3597
3598enum FocusMoveRequestResult {
3599    NotHandled,
3600    Moved,
3601    Retry(FocusDirection),
3602}
3603
3604fn try_dispatch_focus_move_request(
3605    tree: &ComponentNodeTree,
3606    direction: FocusDirection,
3607    focus_owner: &mut FocusOwner,
3608) -> FocusMoveRequestResult {
3609    if !focus_owner.allows_navigation() {
3610        return FocusMoveRequestResult::NotHandled;
3611    }
3612    if focus_owner.move_focus(direction) {
3613        return FocusMoveRequestResult::Moved;
3614    }
3615    if dispatch_focus_beyond_bounds_request(tree, direction, focus_owner) {
3616        return FocusMoveRequestResult::Retry(direction);
3617    }
3618    FocusMoveRequestResult::NotHandled
3619}
3620
3621fn dispatch_focus_beyond_bounds_request(
3622    tree: &ComponentNodeTree,
3623    direction: FocusDirection,
3624    focus_owner: &FocusOwner,
3625) -> bool {
3626    let mut current = focus_owner.active_component_node_id();
3627    while let Some(node_id) = current {
3628        let Some(node_ref) = tree.get(node_id) else {
3629            break;
3630        };
3631        let node = node_ref.get();
3632        if let Some(handler) = &node.interaction().focus_beyond_bounds_handler
3633            && handler.call(direction)
3634        {
3635            return true;
3636        }
3637        current = node_ref.parent();
3638    }
3639    false
3640}
3641
3642fn needs_deferred_focus_retry(focus_owner: &FocusOwner, has_queued_input: bool) -> bool {
3643    focus_owner.keyboard_is_deferred() && (has_queued_input || focus_owner.has_pending_reveal())
3644}
3645
3646pub(crate) fn dispatch_pending_focus_reveal_request(
3647    tree: &ComponentNodeTree,
3648    metadatas: &ComponentNodeMetaDatas,
3649    focus_owner: &mut FocusOwner,
3650    retry_active_focus: bool,
3651) -> bool {
3652    let handle_id = focus_owner.take_pending_reveal().or_else(|| {
3653        retry_active_focus
3654            .then(|| focus_owner.active_handle_id())
3655            .flatten()
3656    });
3657    let Some(handle_id) = handle_id else {
3658        return false;
3659    };
3660    dispatch_focus_reveal_request(tree, metadatas, focus_owner, handle_id)
3661}
3662
3663fn dispatch_focus_reveal_request(
3664    tree: &ComponentNodeTree,
3665    metadatas: &ComponentNodeMetaDatas,
3666    focus_owner: &FocusOwner,
3667    handle_id: FocusHandleId,
3668) -> bool {
3669    let Some(target_node_id) = focus_owner.component_node_id_of(handle_id) else {
3670        return false;
3671    };
3672    let Some(target_bounds) = component_node_bounds_precise(metadatas, target_node_id) else {
3673        return false;
3674    };
3675
3676    let mut current = Some(target_node_id);
3677    while let Some(node_id) = current {
3678        let Some(node_ref) = tree.get(node_id) else {
3679            break;
3680        };
3681        let Some(viewport_rect) = component_node_viewport_rect(metadatas, node_id) else {
3682            current = node_ref.parent();
3683            continue;
3684        };
3685        let node = node_ref.get();
3686        if let Some(handler) = &node.interaction().focus_reveal_handler {
3687            let metadata = metadatas
3688                .get(&node_id)
3689                .expect("Reveal container must have metadata");
3690            let origin = metadata.abs_position.unwrap_or(PxPosition::ZERO);
3691            let transform = metadata
3692                .local_to_window
3693                .unwrap_or_else(|| crate::LocalToWindow::new(1.0, origin).unwrap());
3694            let viewport_bounds = component_node_bounds_precise(metadatas, node_id)
3695                .and_then(|bounds| {
3696                    metadata
3697                        .event_clip_bounds
3698                        .map_or(Some(bounds), |clip| clip.intersection(bounds))
3699                })
3700                .unwrap_or_else(|| viewport_rect.into());
3701            let handled = handler.call(crate::focus::FocusRevealRequest::with_geometry(
3702                target_bounds,
3703                viewport_bounds,
3704                transform,
3705            ));
3706            if handled {
3707                return true;
3708            }
3709        }
3710        current = node_ref.parent();
3711    }
3712    false
3713}
3714
3715fn component_node_bounds_precise(
3716    metadatas: &ComponentNodeMetaDatas,
3717    node_id: NodeId,
3718) -> Option<crate::FloatRect> {
3719    let metadata = metadatas.get(&node_id)?;
3720    let origin = metadata.abs_position?;
3721    let size = metadata.computed_data?;
3722    let transform = metadata
3723        .local_to_window
3724        .unwrap_or_else(|| crate::LocalToWindow::new(1.0, origin).unwrap());
3725    Some(transform.map_rect(crate::FloatRect::from_position_size(
3726        PxPosition::ZERO,
3727        PxSize::new(size.width, size.height),
3728    )))
3729}
3730
3731fn component_node_bounds_rect(
3732    metadatas: &ComponentNodeMetaDatas,
3733    node_id: indextree::NodeId,
3734) -> Option<PxRect> {
3735    let metadata = metadatas.get(&node_id)?;
3736    let _ = metadata;
3737    component_node_bounds_precise(metadatas, node_id).map(crate::FloatRect::outward)
3738}
3739
3740fn component_node_viewport_rect(
3741    metadatas: &ComponentNodeMetaDatas,
3742    node_id: indextree::NodeId,
3743) -> Option<PxRect> {
3744    let node_rect = component_node_bounds_rect(metadatas, node_id)?;
3745    let metadata = metadatas.get(&node_id)?;
3746    Some(
3747        metadata
3748            .event_clip_rect()
3749            .and_then(|clip_rect| clip_rect.intersection(&node_rect))
3750            .unwrap_or(node_rect),
3751    )
3752}
3753
3754fn collect_dirty_layout_paths(
3755    tree: &ComponentNodeTree,
3756    node_ids: &HashMap<u64, indextree::NodeId>,
3757    measure_self: &HashSet<u64>,
3758    placement_self: &HashSet<u64>,
3759) -> HashMap<NodeId, DirtyLayoutPath> {
3760    use std::collections::hash_map::Entry;
3761
3762    let mut paths: HashMap<_, DirtyLayoutPath> = HashMap::default();
3763    for dirty_key in measure_self
3764        .iter()
3765        .chain(placement_self.difference(measure_self))
3766    {
3767        let mut current = node_ids.get(dirty_key).copied();
3768        let mut layout_child = None;
3769        while let Some(node_id) = current {
3770            let Some(node) = tree.get(node_id) else { break };
3771            let data = node.get();
3772            current = node.parent();
3773            if data.role != NodeRole::Layout && current.is_some() {
3774                continue;
3775            }
3776            let (path, new) = match paths.entry(node_id) {
3777                Entry::Occupied(entry) => (entry.into_mut(), false),
3778                Entry::Vacant(entry) => (
3779                    entry.insert(DirtyLayoutPath {
3780                        measure: measure_self.contains(&data.instance_key),
3781                        placement: placement_self.contains(&data.instance_key),
3782                        children: SmallVec::new(),
3783                    }),
3784                    true,
3785                ),
3786            };
3787            if let Some(child) = layout_child
3788                && !path.measure
3789            {
3790                path.children.push(child);
3791            }
3792            // A previously visited parent already carries its own path to the root.
3793            if !new {
3794                break;
3795            }
3796            layout_child = Some(node_id);
3797        }
3798    }
3799    paths
3800}
3801
3802fn record_layout_commands(
3803    root_node: indextree::NodeId,
3804    tree: &ComponentNodeTree,
3805    metadatas: &mut ComponentNodeMetaDatas,
3806) {
3807    struct DrawModifierChainContent<'a> {
3808        instance: u64,
3809        draw_nodes: &'a [(Arc<dyn crate::modifier::DrawModifierNode>, DrawLayoutBounds)],
3810        node_draw: &'a dyn crate::modifier::DrawModifierNode,
3811        node_bounds: Option<DrawLayoutBounds>,
3812        parent_bounds: DrawLayoutBounds,
3813        pending_start: usize,
3814    }
3815
3816    struct TerminalDrawContent;
3817
3818    impl DrawModifierContent for TerminalDrawContent {
3819        fn draw(&mut self, _input: &mut RenderInput<'_>) {}
3820    }
3821
3822    impl DrawModifierContent for DrawModifierChainContent<'_> {
3823        fn draw(&mut self, input: &mut RenderInput<'_>) {
3824            input.finish_draw_range(self.pending_start, self.parent_bounds);
3825            if let Some(((draw_modifier, bounds), remaining)) = self.draw_nodes.split_first() {
3826                input.draw_bounds = *bounds;
3827                let start = input.recorded_op_count();
3828                let mut draw_ctx = DrawModifierContext {
3829                    render_input: input,
3830                };
3831                let mut next = DrawModifierChainContent {
3832                    instance: self.instance,
3833                    draw_nodes: remaining,
3834                    node_draw: self.node_draw,
3835                    node_bounds: self.node_bounds,
3836                    parent_bounds: *bounds,
3837                    pending_start: start,
3838                };
3839                crate::work::record(
3840                    self.instance,
3841                    crate::WorkPhase::Paint,
3842                    draw_modifier.diagnostic_name(),
3843                );
3844                draw_modifier.draw(&mut draw_ctx, &mut next);
3845                input.finish_draw_range(next.pending_start, *bounds);
3846            } else if let Some(bounds) = self.node_bounds {
3847                input.draw_bounds = bounds;
3848                let start = input.recorded_op_count();
3849                let mut draw_ctx = DrawModifierContext {
3850                    render_input: input,
3851                };
3852                crate::work::record(
3853                    self.instance,
3854                    crate::WorkPhase::Paint,
3855                    self.node_draw.diagnostic_name(),
3856                );
3857                self.node_draw.draw(&mut draw_ctx, &mut TerminalDrawContent);
3858                input.finish_draw_range(start, bounds);
3859            }
3860            input.draw_bounds = self.parent_bounds;
3861            self.pending_start = input.recorded_op_count();
3862        }
3863    }
3864
3865    let pending = std::mem::take(&mut metadatas.paint_dirty_nodes);
3866    for node_id in pending {
3867        let Some(node) = tree.get(node_id).filter(|node| !node.is_removed()) else {
3868            continue;
3869        };
3870        let visible = node_id.ancestors(tree).all(|ancestor| {
3871            tree[ancestor].get().role != NodeRole::Layout
3872                || metadatas.get(&ancestor).is_some_and(|metadata| {
3873                    metadata.computed_data.is_some()
3874                        && (ancestor == root_node || metadata.rel_position.is_some())
3875                })
3876        });
3877        if !visible {
3878            continue;
3879        }
3880        let _phase = push_phase(RuntimePhase::Paint);
3881        let _instance = push_current_component_instance_key(node.get().instance_key);
3882        crate::runtime::clear_phase_state_reads(
3883            node.get().instance_key,
3884            crate::runtime::ReadPhase::Paint,
3885        );
3886        #[cfg(feature = "profiling")]
3887        let _record_profiler_guard = {
3888            let parent_id = node.parent();
3889            Some(ProfilerScopeGuard::new(
3890                ProfilerPhase::Record,
3891                Some(node_id),
3892                parent_id,
3893                Some(node.get().fn_name),
3894            ))
3895        };
3896        // Recording also runs on layout cache hits, so visual state must be
3897        // reset independently of measurement invalidation.
3898        let previous_visual = metadatas.get(&node_id).map(|metadata| {
3899            (
3900                metadata.clips_children,
3901                metadata.clip_region().copied(),
3902                metadata.opacity,
3903            )
3904        });
3905        if let Some(metadata) = metadatas.get_mut(&node_id) {
3906            metadata.fragment = Default::default();
3907            metadata.clips_children = false;
3908            metadata.set_clip_region(None);
3909            metadata.opacity = 1.0;
3910        }
3911        let mut input = RenderInput::new(node_id, metadatas);
3912        let node_ref = node.get();
3913        let mut bounds = Some(input.draw_bounds);
3914        let mut layer_index = 0;
3915        let mut draw_nodes = Vec::new();
3916        let layers = input.draw_layers();
3917        for action in node_ref.modifier.ordered_actions() {
3918            match action {
3919                OrderedModifierAction::Layout => {
3920                    bounds = layers.get(layer_index).copied().flatten();
3921                    layer_index += 1;
3922                }
3923                OrderedModifierAction::Draw(node) => {
3924                    if let Some(bounds) = bounds {
3925                        draw_nodes.push((node, bounds));
3926                    }
3927                }
3928                _ => {}
3929            }
3930        }
3931        let mut content = DrawModifierChainContent {
3932            instance: node_ref.instance_logic_id,
3933            draw_nodes: &draw_nodes,
3934            node_draw: node_ref.draw_node.as_ref(),
3935            node_bounds: bounds,
3936            parent_bounds: input.draw_bounds,
3937            pending_start: 0,
3938        };
3939        content.draw(&mut input);
3940        metadatas.paint_changed.insert(node_id);
3941        if let Some(metadata) = metadatas.get_mut(&node_id) {
3942            metadata.paint_dirty = false;
3943        }
3944        let next_visual = metadatas.get(&node_id).map(|metadata| {
3945            (
3946                metadata.clips_children,
3947                metadata.clip_region().copied(),
3948                metadata.opacity,
3949            )
3950        });
3951        if previous_visual != next_visual {
3952            metadatas.mark_geometry_dirty(node_id);
3953        }
3954    }
3955}
3956
3957#[derive(Clone, Copy, PartialEq)]
3958struct PreparedLayoutMetadata {
3959    local_position: PxPosition,
3960    local_to_window: crate::LocalToWindow,
3961    child_local_to_window: crate::LocalToWindow,
3962    child_clip_bounds: Option<crate::FloatRect>,
3963    size: PxSize,
3964    clips_children: bool,
3965    cumulative_opacity: f32,
3966    has_shape_clipping: bool,
3967}
3968
3969impl PreparedLayoutMetadata {
3970    fn child_geometry_eq(&self, other: &Self) -> bool {
3971        self.child_local_to_window == other.child_local_to_window
3972            && self.child_clip_bounds == other.child_clip_bounds
3973            && self.cumulative_opacity == other.cumulative_opacity
3974            && self.has_shape_clipping == other.has_shape_clipping
3975    }
3976}
3977
3978fn map_clip_region(
3979    mut region: crate::ClipRegion,
3980    transform: crate::LocalToWindow,
3981) -> crate::ClipRegion {
3982    region.position = transform.map_point(region.position);
3983    region.size = [
3984        region.size[0] * transform.scale(),
3985        region.size[1] * transform.scale(),
3986    ];
3987    if let crate::ClipShape::RoundedRectangle { ref mut radii, .. } = region.shape {
3988        for radius in radii {
3989            *radius *= transform.scale();
3990        }
3991    }
3992    region
3993}
3994
3995fn record_frame_stage(timer: &mut Option<Instant>, stage: &'static str) {
3996    if let Some(start) = timer {
3997        tracing::debug!(target: "tessera_ui::frame_cost", stage, duration_ns = start.elapsed().as_nanos(), "frame_stage_cpu");
3998        *start = Instant::now();
3999    }
4000}
4001
4002fn geometry_parent(tree: &ComponentNodeTree, node_id: NodeId) -> Option<NodeId> {
4003    node_id
4004        .ancestors(tree)
4005        .skip(1)
4006        .find(|&id| tree[id].get().role == NodeRole::Layout || tree[id].parent().is_none())
4007}
4008
4009fn layout_rect_in_space(
4010    tree: &ComponentNodeTree,
4011    metadata: &ComponentNodeMetaDatas,
4012    node_id: NodeId,
4013) -> Option<crate::FloatRect> {
4014    let size = metadata.get(&node_id)?.prepared_layout?.size;
4015    let mut transform = crate::LocalToWindow::IDENTITY;
4016    let mut child_position = None;
4017    for id in node_id.ancestors(tree) {
4018        let node = tree[id].get();
4019        if node.role != NodeRole::Layout {
4020            continue;
4021        }
4022        let prepared = metadata.get(&id)?.prepared_layout?;
4023        if let Some(position) = child_position {
4024            transform = crate::LocalToWindow::new(node.children_scale, PxPosition::ZERO)?
4025                .with_local_origin(position)
4026                .compose(transform)?;
4027        }
4028        if node.layout_coordinate_space {
4029            return Some(
4030                transform.map_rect(crate::FloatRect::from_position_size(PxPosition::ZERO, size)),
4031            );
4032        }
4033        child_position = Some(prepared.local_position);
4034    }
4035    None
4036}
4037
4038fn prepare_layout_metadata_for_node(
4039    node: &ComponentNode,
4040    metadata: &mut ComponentNodeMetaData,
4041    parent_transform: crate::LocalToWindow,
4042    is_root: bool,
4043    parent_clip_bounds: Option<crate::FloatRect>,
4044    current_opacity: f32,
4045    parent_has_shape_clipping: bool,
4046    reuse_local_placement: bool,
4047) -> (Option<PreparedLayoutMetadata>, bool) {
4048    metadata.has_shape_clipping = parent_has_shape_clipping || metadata.clip_region().is_some();
4049    let rel_pos = match metadata.rel_position {
4050        Some(pos) => pos,
4051        None if is_root => PxPosition::ZERO,
4052        _ => {
4053            metadata.base_abs_position = None;
4054            metadata.abs_position = None;
4055            metadata.local_to_window = None;
4056            metadata.event_clip_bounds = None;
4057            metadata.prepared_layout = None;
4058            metadata.set_prepared_clip_region(None);
4059            metadata.geometry_initialized = true;
4060            return (None, false);
4061        }
4062    };
4063    let base_self_position = parent_transform.map_point(rel_pos);
4064    let cached_position = reuse_local_placement
4065        .then(|| {
4066            metadata
4067                .prepared_layout
4068                .as_ref()
4069                .map(|previous| previous.local_position)
4070        })
4071        .flatten();
4072    let local_position = if let Some(position) = cached_position {
4073        if let Some(transforms) = &mut metadata.input_transforms {
4074            transforms.parent = parent_transform;
4075        }
4076        position
4077    } else {
4078        let _phase = push_phase(RuntimePhase::Place);
4079        let _instance = push_current_component_instance_key(node.instance_key);
4080        crate::runtime::clear_phase_state_reads(
4081            node.instance_key,
4082            crate::runtime::ReadPhase::Transform,
4083        );
4084        let (position, inputs) = node
4085            .modifier
4086            .resolve_placement(rel_pos, node.instance_logic_id);
4087        metadata.set_input_transforms(parent_transform, inputs);
4088        position
4089    };
4090    let local_to_window = parent_transform.with_local_origin(local_position);
4091    let self_position = local_to_window.translation();
4092    let child_scale = node.children_scale;
4093    let child_local_to_window = if child_scale == 1.0 {
4094        local_to_window
4095    } else {
4096        local_to_window
4097            .compose(
4098                crate::LocalToWindow::new(child_scale, PxPosition::ZERO)
4099                    .expect("Child scale must be finite and positive"),
4100            )
4101            .expect("Composed child transform must remain finite and invertible")
4102    };
4103    metadata.local_to_window = Some(local_to_window);
4104    let cumulative_opacity = current_opacity * metadata.opacity;
4105    metadata.base_abs_position = Some(base_self_position);
4106    metadata.abs_position = Some(self_position);
4107    metadata.event_clip_bounds = parent_clip_bounds;
4108
4109    let size = metadata
4110        .computed_data
4111        .map(|d| PxSize {
4112            width: d.width,
4113            height: d.height,
4114        })
4115        .unwrap_or_default();
4116    let clips_children = metadata.clips_children;
4117    let clip_region = metadata
4118        .clip_region()
4119        .map(|&region| map_clip_region(region, local_to_window));
4120    let previous_shape = metadata
4121        .prepared_layout
4122        .as_ref()
4123        .filter(|layout| layout.clips_children)
4124        .and(metadata.prepared_clip_region());
4125    let next_shape = clips_children.then_some(clip_region.as_ref()).flatten();
4126    let shape_changed = previous_shape != next_shape;
4127    let child_clip_bounds = if clips_children {
4128        let own_clip_bounds = clip_region
4129            .map(|region| crate::FloatRect {
4130                x: region.position.x,
4131                y: region.position.y,
4132                width: region.size[0],
4133                height: region.size[1],
4134            })
4135            .unwrap_or_else(|| {
4136                local_to_window
4137                    .map_rect(crate::FloatRect::from_position_size(PxPosition::ZERO, size))
4138            });
4139        Some(parent_clip_bounds.map_or(own_clip_bounds, |parent| {
4140            parent
4141                .intersection(own_clip_bounds)
4142                .unwrap_or(crate::FloatRect::ZERO)
4143        }))
4144    } else {
4145        parent_clip_bounds
4146    };
4147    let prepared = PreparedLayoutMetadata {
4148        local_position,
4149        local_to_window,
4150        child_local_to_window,
4151        child_clip_bounds,
4152        size,
4153        clips_children,
4154        cumulative_opacity,
4155        has_shape_clipping: metadata.has_shape_clipping,
4156    };
4157    metadata.prepared_layout = Some(prepared);
4158    metadata.set_prepared_clip_region(clip_region);
4159    metadata.geometry_initialized = true;
4160    (Some(prepared), shape_changed)
4161}
4162
4163fn populate_layout_metadata(
4164    root_node: indextree::NodeId,
4165    tree: &ComponentNodeTree,
4166    metadatas: &mut ComponentNodeMetaDatas,
4167) -> u64 {
4168    let order_dirty = std::mem::take(&mut metadatas.placement_order_dirty);
4169    for id in order_dirty {
4170        if let Some(node) = tree.get(id).filter(|node| !node.is_removed()) {
4171            let id = if node.get().role == NodeRole::Layout || id == root_node {
4172                Some(id)
4173            } else {
4174                geometry_parent(tree, id)
4175            };
4176            if let Some(id) = id {
4177                metadatas.placement_order_dirty.insert(id);
4178                metadatas.geometry_dirty.insert(id);
4179            }
4180        }
4181    }
4182    let requested = std::mem::take(&mut metadatas.geometry_dirty);
4183    let mut dirty: HashSet<_> = requested
4184        .into_iter()
4185        .filter_map(|id| {
4186            let node = tree.get(id).filter(|node| !node.is_removed())?;
4187            if node.get().role == NodeRole::Layout || id == root_node {
4188                Some(id)
4189            } else {
4190                None
4191            }
4192        })
4193        .collect();
4194    let mut dirty_children: HashMap<NodeId, HashSet<NodeId>> = HashMap::default();
4195    for mut id in dirty.iter().copied().collect::<Vec<_>>() {
4196        while let Some(parent) = geometry_parent(tree, id) {
4197            dirty_children.entry(parent).or_default().insert(id);
4198            if !dirty.insert(parent) {
4199                break;
4200            }
4201            id = parent;
4202        }
4203    }
4204    fn visit(
4205        tree: &ComponentNodeTree,
4206        metadatas: &mut ComponentNodeMetaDatas,
4207        dirty: &HashSet<indextree::NodeId>,
4208        dirty_children: &HashMap<NodeId, HashSet<NodeId>>,
4209        parent_transform: crate::LocalToWindow,
4210        is_root: bool,
4211        node_id: indextree::NodeId,
4212        clip_bounds: Option<crate::FloatRect>,
4213        current_opacity: f32,
4214        parent_has_shape_clipping: bool,
4215        inherited_order_changed: bool,
4216        inherited_shape_clip_changed: bool,
4217        inherited_changed: bool,
4218        updates: &mut u64,
4219    ) {
4220        metadatas.geometry_visits += 1;
4221        let Some(node) = tree.get(node_id).map(|node| node.get()) else {
4222            return;
4223        };
4224        let self_dirty = dirty.contains(&node_id);
4225        let (
4226            previous,
4227            was_hidden,
4228            initialized,
4229            paint_dirty,
4230            prepared,
4231            publishes_geometry,
4232            size_changed,
4233            own_shape_changed,
4234        ) = if let Some(metadata) = metadatas.get_mut(&node_id) {
4235            let initialized = metadata.geometry_initialized;
4236            if initialized && !inherited_changed && !self_dirty {
4237                return;
4238            }
4239            let previous = metadata.prepared_layout;
4240            let was_placed = metadata.abs_position.is_some();
4241            let was_hidden = metadata.prepared_layout.is_none();
4242            let paint_dirty = metadata.paint_dirty;
4243            let (prepared, own_shape_changed) = prepare_layout_metadata_for_node(
4244                node,
4245                metadata,
4246                parent_transform,
4247                is_root,
4248                clip_bounds,
4249                current_opacity,
4250                parent_has_shape_clipping,
4251                initialized && !self_dirty,
4252            );
4253            let publishes_geometry = metadata.observes_geometry
4254                || metadata.accessibility.is_some()
4255                || metadata.clips_children
4256                || !metadata.fragment.ops().is_empty();
4257            let size_changed = metadata.observes_size
4258                && (was_placed != prepared.is_some()
4259                    || previous.map(|layout| layout.size) != prepared.map(|layout| layout.size));
4260            (
4261                previous,
4262                was_hidden,
4263                initialized,
4264                paint_dirty,
4265                prepared,
4266                publishes_geometry,
4267                size_changed,
4268                own_shape_changed,
4269            )
4270        } else {
4271            (None, true, false, false, None, false, false, false)
4272        };
4273        *updates += 1;
4274        if size_changed {
4275            metadatas.size_changed.insert(node_id);
4276        }
4277        if initialized && was_hidden && prepared.is_none() && !self_dirty {
4278            return;
4279        }
4280        let order_changed = metadatas.placement_order_dirty.remove(&node_id);
4281        let order_changed = order_changed || inherited_order_changed;
4282        if order_changed {
4283            metadatas.placement_order_changed.insert(node_id);
4284        }
4285        let changed = inherited_changed
4286            || !initialized
4287            || prepared != previous
4288            || order_changed
4289            || own_shape_changed;
4290        if changed && publishes_geometry {
4291            metadatas.geometry_changed.insert(node_id);
4292        }
4293        if prepared.is_some() && paint_dirty {
4294            metadatas.paint_dirty_nodes.insert(node_id);
4295        }
4296        if prepared.is_none() && node.role == NodeRole::Layout {
4297            for descendant in node_id.descendants(tree) {
4298                let size_hidden = if let Some(metadata) = metadatas.get_mut(&descendant) {
4299                    let size_hidden = metadata.observes_size && metadata.abs_position.is_some();
4300                    metadata.base_abs_position = None;
4301                    metadata.abs_position = None;
4302                    metadata.local_to_window = None;
4303                    metadata.event_clip_bounds = None;
4304                    size_hidden
4305                } else {
4306                    false
4307                };
4308                if size_hidden {
4309                    metadatas.size_changed.insert(descendant);
4310                }
4311                metadatas.geometry_changed.insert(descendant);
4312            }
4313            return;
4314        }
4315        let (transform, clip, opacity) = prepared.map_or(
4316            (parent_transform, clip_bounds, current_opacity),
4317            |prepared| {
4318                (
4319                    prepared.child_local_to_window,
4320                    prepared.child_clip_bounds,
4321                    prepared.cumulative_opacity,
4322                )
4323            },
4324        );
4325        let has_shape_clipping = prepared.map_or(parent_has_shape_clipping, |prepared| {
4326            prepared.has_shape_clipping
4327        });
4328        // Shape changes can invalidate descendant hit tests without changing
4329        // their rectangular clip bounds or coordinate transforms.
4330        let shape_clip_changed = inherited_shape_clip_changed || own_shape_changed;
4331        let children_changed = !initialized
4332            || order_changed
4333            || shape_clip_changed
4334            || match (previous, prepared) {
4335                (Some(previous), Some(prepared)) => !previous.child_geometry_eq(&prepared),
4336                (None, None) => false,
4337                _ => true,
4338            };
4339        let mut visit_child = |child| {
4340            visit(
4341                tree,
4342                metadatas,
4343                dirty,
4344                dirty_children,
4345                transform,
4346                false,
4347                child,
4348                clip,
4349                opacity,
4350                has_shape_clipping,
4351                order_changed,
4352                shape_clip_changed,
4353                children_changed,
4354                updates,
4355            );
4356        };
4357        if children_changed {
4358            if let Some(children) = node.layout_children.as_ref() {
4359                for &child in children.iter() {
4360                    visit_child(child);
4361                }
4362            } else {
4363                for &child in direct_layout_children(node_id, tree).iter() {
4364                    visit_child(child);
4365                }
4366            }
4367        } else if let Some(children) = dirty_children.get(&node_id) {
4368            for &child in children {
4369                visit_child(child);
4370            }
4371        }
4372    }
4373    let mut updates = 0;
4374    visit(
4375        tree,
4376        metadatas,
4377        &dirty,
4378        &dirty_children,
4379        crate::LocalToWindow::IDENTITY,
4380        true,
4381        root_node,
4382        None,
4383        1.0,
4384        false,
4385        false,
4386        false,
4387        false,
4388        &mut updates,
4389    );
4390    updates
4391}
4392
4393#[cfg(test)]
4394fn build_render_graph(
4395    root: NodeId,
4396    tree: &ComponentNodeTree,
4397    metadata: &mut ComponentNodeMetaDatas,
4398    viewport: PxSize,
4399) -> RenderGraph {
4400    let changed = root.descendants(tree).collect();
4401    scene::FrameScene::default()
4402        .update(
4403            root,
4404            tree,
4405            metadata,
4406            &changed,
4407            &HashSet::default(),
4408            viewport,
4409        )
4410        .0
4411}
4412
4413#[cfg(test)]
4414mod tests {
4415    use super::*;
4416
4417    use crate::{
4418        Px,
4419        component_tree::{ComponentNode, NodeRole},
4420    };
4421
4422    fn node_with_role(
4423        name: &'static str,
4424        role: NodeRole,
4425        instance_logic_id: u64,
4426        instance_key: u64,
4427    ) -> ComponentNode {
4428        ComponentNode {
4429            instance_logic_id,
4430            instance_key,
4431            ..ComponentNode::new(name, role)
4432        }
4433    }
4434
4435    fn node(name: &'static str, instance_logic_id: u64, instance_key: u64) -> ComponentNode {
4436        node_with_role(name, NodeRole::Layout, instance_logic_id, instance_key)
4437    }
4438
4439    #[test]
4440    fn unchanged_child_coordinates_stop_geometry_propagation_but_shape_changes_reach_inputs() {
4441        let _runtime_session = crate::RuntimeSession::new();
4442        let _runtime_scope = _runtime_session.enter();
4443        let mut tree = ComponentTree::new();
4444        let root = tree.add_node(node("root", 1, 1));
4445        let moving = tree.add_node(node("moving", 2, 2));
4446        let pinned = tree.add_node(node("pinned", 3, 3));
4447        let inner = tree.add_node(node("inner", 4, 4));
4448        let mut control = node("control", 5, 5);
4449        control
4450            .interaction_mut()
4451            .pointer_handlers
4452            .push(Box::new(|_| {}));
4453        let leaf = tree.add_node(control);
4454        for _ in 0..5 {
4455            tree.pop_node();
4456        }
4457        for id in [root, moving, pinned, inner, leaf] {
4458            let metadata = tree.metadatas.entry_or_default(id);
4459            metadata.computed_data = Some(ComputedData {
4460                width: Px(100),
4461                height: Px(100),
4462            });
4463            metadata.rel_position = Some(PxPosition::ZERO);
4464            metadata.placement_order = Some(0);
4465        }
4466        node::place_node(leaf, PxPosition::new(Px(2), Px(2)), 0, &mut tree.metadatas);
4467        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4468        tree.metadatas.geometry_changed.clear();
4469        tree.metadatas.geometry_visits = 0;
4470
4471        node::place_node(
4472            moving,
4473            PxPosition::new(Px(35), Px(0)),
4474            0,
4475            &mut tree.metadatas,
4476        );
4477        node::place_node(
4478            pinned,
4479            PxPosition::new(Px(-35), Px(0)),
4480            0,
4481            &mut tree.metadatas,
4482        );
4483        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4484        assert_eq!(
4485            tree.metadatas.get(&leaf).unwrap().abs_position,
4486            Some(PxPosition::new(Px(2), Px(2)))
4487        );
4488        assert!(
4489            !tree.metadatas.geometry_changed.contains(&leaf),
4490            "a counter-translated branch must not invalidate unchanged descendant input geometry"
4491        );
4492        assert_eq!(tree.metadatas.geometry_visits, 3);
4493
4494        tree.metadatas.geometry_changed.clear();
4495        tree.metadatas.entry_or_default(pinned).computed_data = Some(ComputedData {
4496            width: Px(120),
4497            height: Px(100),
4498        });
4499        tree.metadatas.mark_geometry_dirty(pinned);
4500        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4501        assert!(
4502            !tree.metadatas.geometry_changed.contains(&leaf),
4503            "an unclipped parent's size must not invalidate unchanged child coordinates"
4504        );
4505
4506        node::place_node(leaf, PxPosition::new(Px(3), Px(3)), 0, &mut tree.metadatas);
4507        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4508        assert_eq!(
4509            tree.metadatas.get(&leaf).unwrap().abs_position,
4510            Some(PxPosition::new(Px(3), Px(3)))
4511        );
4512        assert!(tree.metadatas.geometry_changed.contains(&leaf));
4513
4514        let clip = crate::ClipRegion {
4515            position: PxPosition::ZERO,
4516            size: [100.0, 100.0],
4517            shape: crate::ClipShape::RoundedRectangle {
4518                radii: [0.0; 4],
4519                exponents: [2.0; 4],
4520            },
4521        };
4522        let metadata = tree.metadatas.entry_or_default(root);
4523        metadata.clips_children = true;
4524        metadata.set_clip_region(Some(clip));
4525        tree.metadatas.mark_geometry_dirty(root);
4526        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4527        assert!(inside_shape_clips(
4528            &tree.tree,
4529            &tree.metadatas,
4530            leaf,
4531            PxPosition::new(Px(4), Px(4))
4532        ));
4533        tree.metadatas.geometry_changed.clear();
4534
4535        tree.metadatas
4536            .entry_or_default(root)
4537            .set_clip_region(Some(crate::ClipRegion {
4538                shape: crate::ClipShape::RoundedRectangle {
4539                    radii: [40.0; 4],
4540                    exponents: [2.0; 4],
4541                },
4542                ..clip
4543            }));
4544        tree.metadatas.mark_geometry_dirty(root);
4545        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4546        assert!(tree.metadatas.geometry_changed.contains(&leaf));
4547        assert!(!inside_shape_clips(
4548            &tree.tree,
4549            &tree.metadatas,
4550            leaf,
4551            PxPosition::new(Px(4), Px(4))
4552        ));
4553
4554        tree.metadatas.geometry_changed.clear();
4555        tree.metadatas.entry_or_default(root).set_clip_region(None);
4556        tree.metadatas.mark_geometry_dirty(root);
4557        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4558        assert!(tree.metadatas.geometry_changed.contains(&leaf));
4559        assert!(inside_shape_clips(
4560            &tree.tree,
4561            &tree.metadatas,
4562            leaf,
4563            PxPosition::new(Px(4), Px(4))
4564        ));
4565
4566        tree.metadatas.entry_or_default(pinned).rel_position = None;
4567        tree.metadatas.mark_geometry_dirty(pinned);
4568        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4569        assert!(tree.metadatas.get(&leaf).unwrap().abs_position.is_none());
4570        tree.metadatas.entry_or_default(inner).geometry_initialized = false;
4571        node::place_node(
4572            pinned,
4573            PxPosition::new(Px(-35), Px(0)),
4574            0,
4575            &mut tree.metadatas,
4576        );
4577        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4578        assert_eq!(
4579            tree.metadatas.get(&leaf).unwrap().abs_position,
4580            Some(PxPosition::new(Px(3), Px(3)))
4581        );
4582    }
4583
4584    #[derive(Clone, Default, PartialEq)]
4585    struct RevealProbe {
4586        handler: crate::testing::TestProbe<
4587            Option<crate::CallbackWith<crate::focus::FocusRevealRequest, bool>>,
4588        >,
4589        scroll: crate::testing::TestProbe<i32>,
4590        rects: crate::testing::TestProbe<Vec<crate::PxRect>>,
4591    }
4592
4593    #[crate::tessera(crate)]
4594    fn retry_reveal_callback_fixture(probe: RevealProbe) {
4595        let handler = crate::CallbackWith::new({
4596            let probe = probe.clone();
4597            move |request: crate::focus::FocusRevealRequest| {
4598                probe
4599                    .rects
4600                    .with_mut(|rects| rects.push(request.target_rect));
4601                probe.scroll.set(-request.target_rect.y.0);
4602                true
4603            }
4604        });
4605        probe.handler.set(Some(handler));
4606        crate::layout::layout();
4607    }
4608
4609    fn reveal_viewport_tree(
4610        probe: RevealProbe,
4611    ) -> (
4612        ComponentTree,
4613        NodeId,
4614        crate::focus::FocusRegistration,
4615        crate::testing::LayoutTestSession<impl Fn()>,
4616    ) {
4617        use crate::focus::{FocusNode, FocusRegistration};
4618
4619        let mut session = crate::testing::start_layout_test_session(crate::testing::layout_test({
4620            let probe = probe.clone();
4621            move || retry_reveal_callback_fixture(probe.clone())
4622        }));
4623        crate::testing::pump_layout_test_session(&mut session, 0);
4624        probe.rects.with_mut(Vec::clear);
4625        probe.scroll.set(0);
4626        let mut tree = ComponentTree::new();
4627        let mut viewport = node("viewport", 1, 1);
4628        viewport.interaction_mut().focus_reveal_handler = probe.handler.get();
4629        let root = tree.add_node(viewport);
4630        let focus = FocusRegistration::target(FocusNode::new());
4631        let mut target = node("focused control", 2, 2);
4632        target.interaction_mut().focus_registration = Some(focus);
4633        let target = tree.add_node(target);
4634        tree.pop_node();
4635        tree.pop_node();
4636        for (id, y, width, height) in [(root, 0, 200, 100), (target, 300, 80, 24)] {
4637            let metadata = tree.metadatas.get_mut(&id).unwrap();
4638            metadata.abs_position = Some(PxPosition::new(Px::ZERO, Px(y)));
4639            metadata.base_abs_position = metadata.abs_position;
4640            metadata.computed_data = Some(ComputedData {
4641                width: Px(width),
4642                height: Px(height),
4643            });
4644        }
4645        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
4646        (tree, root, focus, session)
4647    }
4648
4649    #[test]
4650    fn pending_focus_reveal_retries_a_keyboard_barrier_without_queued_input() {
4651        let _runtime_session = crate::RuntimeSession::new();
4652        let _runtime_scope = _runtime_session.enter();
4653        let probe = RevealProbe::default();
4654        use crate::focus::{FocusManager, bind_focus_owner};
4655
4656        let (mut tree, _, focus, _session) = reveal_viewport_tree(probe.clone());
4657        {
4658            let _binding = bind_focus_owner(&mut tree.focus_owner);
4659            FocusManager::current().defer_keyboard_until_layout();
4660        }
4661        assert!(
4662            !needs_deferred_focus_retry(&tree.focus_owner, false),
4663            "a barrier without pending work must not schedule an idle retry"
4664        );
4665        tree.focus_owner.request_focus(focus.id);
4666        assert!(
4667            needs_deferred_focus_retry(&tree.focus_owner, false),
4668            "a pending reveal must schedule another pass even without keyboard or IME events"
4669        );
4670        tree.focus_owner.begin_layout();
4671        assert!(needs_deferred_focus_retry(&tree.focus_owner, false));
4672        tree.focus_owner.finish_layout();
4673        assert!(
4674            tree.has_pending_focus_reveal(),
4675            "post-layout scheduling must still retry an unconsumed reveal even after the barrier clears"
4676        );
4677        assert!(tree.pending_focused_input.is_empty());
4678        assert!(dispatch_pending_focus_reveal_request(
4679            &tree.tree,
4680            &tree.metadatas,
4681            &mut tree.focus_owner,
4682            false
4683        ));
4684        assert_eq!(
4685            probe.scroll.get(),
4686            -300,
4687            "the settled barrier must allow the real viewport handler to reveal the pending target"
4688        );
4689        assert_eq!(
4690            probe.rects.with(|rects| rects.clone()),
4691            [crate::PxRect::new(Px::ZERO, Px(300), Px(80), Px(24))]
4692        );
4693        assert!(!needs_deferred_focus_retry(&tree.focus_owner, false));
4694        assert!(
4695            !tree.has_pending_focus_reveal(),
4696            "the post-flush scheduler must stop retrying once the viewport has handled the request"
4697        );
4698    }
4699
4700    #[test]
4701    fn reveal_retry_consumes_the_request_before_a_later_pointer_frame() {
4702        let _runtime_session = crate::RuntimeSession::new();
4703        let _runtime_scope = _runtime_session.enter();
4704        let probe = RevealProbe::default();
4705        let (mut tree, root, focus, _session) = reveal_viewport_tree(probe.clone());
4706        tree.focus_owner.request_focus(focus.id);
4707        assert!(dispatch_pending_focus_reveal_request(
4708            &tree.tree,
4709            &tree.metadatas,
4710            &mut tree.focus_owner,
4711            true
4712        ));
4713        assert_eq!(
4714            probe.scroll.get(),
4715            -300,
4716            "the retry must deliver the actual pending reveal to the viewport handler"
4717        );
4718        assert_eq!(
4719            probe.rects.with(|rects| rects.clone()),
4720            [crate::PxRect::new(Px::ZERO, Px(300), Px(80), Px(24))]
4721        );
4722        probe.scroll.set(-600);
4723        let mut pointer = Some(PxPosition::new(Px(180), Px(50)));
4724        let mut changes = [crate::PointerChange {
4725            timestamp: crate::time::Instant::now(),
4726            pointer_id: crate::MOUSE_POINTER_ID,
4727            content: crate::CursorEventContent::Moved(pointer.unwrap()),
4728            gesture_state: crate::GestureState::TapCandidate,
4729            sampled_position: Default::default(),
4730            consumed: false,
4731            tap_claimed: false,
4732            scroll_discrete: None,
4733        }];
4734        tree.dispatch_pointer_input(
4735            root,
4736            &mut changes,
4737            &mut pointer,
4738            winit::keyboard::ModifiersState::empty(),
4739        );
4740        assert!(
4741            !dispatch_pending_focus_reveal_request(
4742                &tree.tree,
4743                &tree.metadatas,
4744                &mut tree.focus_owner,
4745                false
4746            ),
4747            "an ordinary pointer frame must not repeat a reveal already handled by the retry"
4748        );
4749        assert_eq!(
4750            probe.scroll.get(),
4751            -600,
4752            "the later pointer frame preserves the user's new scroll position"
4753        );
4754        assert_eq!(probe.rects.with(Vec::len), 1);
4755        assert!(
4756            dispatch_pending_focus_reveal_request(
4757                &tree.tree,
4758                &tree.metadatas,
4759                &mut tree.focus_owner,
4760                true
4761            ),
4762            "an explicit retry without a new request still visits the active target"
4763        );
4764        assert_eq!(probe.rects.with(Vec::len), 2);
4765    }
4766
4767    #[test]
4768    fn newer_nonzero_scroll_suppresses_only_the_reveal_of_an_older_deferred_focus_command() {
4769        let _runtime_session = crate::RuntimeSession::new();
4770        let _runtime_scope = _runtime_session.enter();
4771        let probe = RevealProbe::default();
4772        use crate::focus::{FocusNode, FocusRegistration};
4773
4774        for delta_y in [0.0, -24.0] {
4775            for newer_request_before_attach in [false, true] {
4776                let (mut tree, root, original, _session) = reveal_viewport_tree(probe.clone());
4777                let original_node = tree.focus_owner.component_node_id_of(original.id).unwrap();
4778                let delayed = FocusRegistration::target(FocusNode::new());
4779                tree.focus_owner.request_focus(delayed.id);
4780                let mut pointer = Some(PxPosition::new(Px(180), Px(50)));
4781                let mut changes = [crate::PointerChange {
4782                    timestamp: crate::time::Instant::now(),
4783                    pointer_id: crate::MOUSE_POINTER_ID,
4784                    content: crate::CursorEventContent::Scroll(crate::cursor::ScrollEventContent {
4785                        delta_x: 0.0,
4786                        delta_y,
4787                        unit: crate::cursor::ScrollDeltaUnit::Pixel,
4788                        source: crate::cursor::ScrollEventSource::Wheel,
4789                    }),
4790                    gesture_state: crate::GestureState::TapCandidate,
4791                    sampled_position: Default::default(),
4792                    consumed: false,
4793                    tap_claimed: false,
4794                    scroll_discrete: None,
4795                }];
4796                tree.dispatch_pointer_input(
4797                    root,
4798                    &mut changes,
4799                    &mut pointer,
4800                    winit::keyboard::ModifiersState::empty(),
4801                );
4802                if newer_request_before_attach {
4803                    tree.focus_owner.request_focus(original.id);
4804                }
4805
4806                let mut target = node("delayed focus target", 3, 3);
4807                target.interaction_mut().focus_registration = Some(delayed);
4808                let target = tree.add_node(target);
4809                tree.pop_node();
4810                root.append(target, &mut tree.tree);
4811                let metadata = tree.metadatas.get_mut(&target).unwrap();
4812                metadata.abs_position = Some(PxPosition::new(Px(0), Px(500)));
4813                metadata.base_abs_position = metadata.abs_position;
4814                metadata.computed_data = Some(ComputedData {
4815                    width: Px(80),
4816                    height: Px(24),
4817                });
4818                tree.focus_owner.sync_from_component_tree(root, &tree.tree);
4819                tree.focus_owner.commit_pending();
4820                assert_eq!(
4821                    tree.focus_owner.active_component_node_id(),
4822                    Some(if newer_request_before_attach {
4823                        original_node
4824                    } else {
4825                        target
4826                    })
4827                );
4828                assert_eq!(
4829                    dispatch_pending_focus_reveal_request(
4830                        &tree.tree,
4831                        &tree.metadatas,
4832                        &mut tree.focus_owner,
4833                        false,
4834                    ),
4835                    newer_request_before_attach || delta_y == 0.0,
4836                    "a zero scroll must preserve the deferred reveal; a newer nonzero scroll must suppress it"
4837                );
4838                assert_eq!(
4839                    probe.rects.with(Vec::len),
4840                    usize::from(newer_request_before_attach || delta_y == 0.0)
4841                );
4842
4843                tree.focus_owner
4844                    .request_focus(if newer_request_before_attach {
4845                        delayed.id
4846                    } else {
4847                        original.id
4848                    });
4849                assert!(dispatch_pending_focus_reveal_request(
4850                    &tree.tree,
4851                    &tree.metadatas,
4852                    &mut tree.focus_owner,
4853                    false,
4854                ));
4855                let revealed_y = if newer_request_before_attach {
4856                    500
4857                } else {
4858                    300
4859                };
4860                assert_eq!(probe.scroll.get(), -revealed_y);
4861                assert_eq!(
4862                    probe.rects.with(|rects| rects.last().copied()),
4863                    Some(crate::PxRect::new(Px(0), Px(revealed_y), Px(80), Px(24)))
4864                );
4865            }
4866        }
4867    }
4868
4869    #[test]
4870    fn retained_scene_keeps_child_drawing_when_parent_paint_appears_and_disappears() {
4871        let _runtime_session = crate::RuntimeSession::new();
4872        let _runtime_scope = _runtime_session.enter();
4873        #[derive(Clone)]
4874        struct Marker(u32);
4875        impl crate::DrawCommand for Marker {
4876            fn apply_opacity(&mut self, _: f32) {}
4877        }
4878        let mut tree = ComponentTree::new();
4879        let root = tree.add_node(node("root", 1, 1));
4880        let wrapper = tree.add_node(node("wrapper", 2, 2));
4881        let visual = tree.add_node(node("visual", 3, 3));
4882        let label = tree.add_node(node("label", 4, 4));
4883        tree.pop_node();
4884        tree.pop_node();
4885        tree.pop_node();
4886        let sibling = tree.add_node(node("sibling", 5, 5));
4887        tree.pop_node();
4888        tree.pop_node();
4889        for id in [root, wrapper, visual, label, sibling] {
4890            let metadata = tree.metadatas.entry_or_default(id);
4891            metadata.computed_data = Some(ComputedData {
4892                width: Px(32),
4893                height: Px(32),
4894            });
4895            metadata.rel_position = Some(PxPosition::ZERO);
4896        }
4897        tree.metadatas
4898            .entry_or_default(label)
4899            .fragment
4900            .push_draw_command(Marker(1));
4901        tree.metadatas
4902            .entry_or_default(sibling)
4903            .fragment
4904            .push_draw_command(Marker(2));
4905        tree.metadatas.entry_or_default(label).rel_position = None;
4906        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4907        let mut scene = scene::FrameScene::default();
4908        let viewport = PxSize::new(Px(100), Px(100));
4909        scene.update(
4910            root,
4911            &tree.tree,
4912            &tree.metadatas,
4913            &root.descendants(&tree.tree).collect(),
4914            &HashSet::default(),
4915            viewport,
4916        );
4917        tree.metadatas.geometry_changed.clear();
4918        node::place_node(label, PxPosition::ZERO, 0, &mut tree.metadatas);
4919        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
4920        let (revealed, _) = scene.update(
4921            root,
4922            &tree.tree,
4923            &tree.metadatas,
4924            &HashSet::default(),
4925            &HashSet::default(),
4926            viewport,
4927        );
4928        assert_eq!(revealed.ops().len(), 2);
4929        tree.metadatas.geometry_changed.clear();
4930        for painted in [true, false, true, false] {
4931            let metadata = tree.metadatas.entry_or_default(visual);
4932            metadata.fragment = Default::default();
4933            if painted {
4934                metadata.fragment.push_draw_command(Marker(3));
4935            }
4936            tree.metadatas.paint_changed.insert(visual);
4937            let (graph, _) = scene.update(
4938                root,
4939                &tree.tree,
4940                &tree.metadatas,
4941                &HashSet::default(),
4942                &HashSet::default(),
4943                viewport,
4944            );
4945            let values = graph
4946                .ops()
4947                .iter()
4948                .filter_map(|op| op.command.downcast_draw_ref::<Marker>())
4949                .map(|marker| marker.0)
4950                .collect::<Vec<_>>();
4951            assert_eq!(values, if painted { vec![3, 1, 2] } else { vec![1, 2] });
4952            tree.metadatas.paint_changed.clear();
4953        }
4954    }
4955
4956    #[test]
4957    fn retained_scene_reuses_fragment_commands_on_translation_and_refreshes_paint() {
4958        let _runtime_session = crate::RuntimeSession::new();
4959        let _runtime_scope = _runtime_session.enter();
4960        use crate::testing::TestProbe;
4961
4962        struct Marker {
4963            clones: TestProbe<usize>,
4964            value: u32,
4965            opacity: f32,
4966        }
4967
4968        impl Clone for Marker {
4969            fn clone(&self) -> Self {
4970                self.clones.with_mut(|clones| *clones += 1);
4971                Self {
4972                    clones: self.clones.clone(),
4973                    value: self.value,
4974                    opacity: self.opacity,
4975                }
4976            }
4977        }
4978
4979        impl crate::DrawCommand for Marker {
4980            fn apply_opacity(&mut self, opacity: f32) {
4981                self.opacity *= opacity;
4982            }
4983        }
4984
4985        let clones = TestProbe::new(0usize);
4986        let mut tree = ComponentTree::new();
4987        let root = tree.add_node(node("root", 1, 1));
4988        let leaf = tree.add_node(node("leaf", 2, 2));
4989        tree.pop_node();
4990        let outside = tree.add_node(node("outside", 3, 3));
4991        tree.pop_node();
4992        let sibling = tree.add_node(node("sibling", 4, 4));
4993        tree.pop_node();
4994        tree.pop_node();
4995        for id in [root, leaf, outside, sibling] {
4996            let metadata = tree.metadatas.entry_or_default(id);
4997            metadata.computed_data = Some(ComputedData {
4998                width: Px(32),
4999                height: Px(32),
5000            });
5001            metadata.rel_position = Some(PxPosition::ZERO);
5002        }
5003        let outside_clones = TestProbe::new(0usize);
5004        let metadata = tree.metadatas.entry_or_default(outside);
5005        metadata.rel_position = Some(PxPosition::from_f32_xy(150.0, 0.0));
5006        metadata.fragment.push_draw_command(Marker {
5007            clones: outside_clones.clone(),
5008            value: 3,
5009            opacity: 1.0,
5010        });
5011        tree.metadatas
5012            .entry_or_default(sibling)
5013            .fragment
5014            .push_draw_command(Marker {
5015                clones: TestProbe::new(0usize),
5016                value: 5,
5017                opacity: 1.0,
5018            });
5019        tree.metadatas
5020            .entry_or_default(leaf)
5021            .fragment
5022            .push_draw_command(Marker {
5023                clones: clones.clone(),
5024                value: 1,
5025                opacity: 1.0,
5026            });
5027        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5028        let viewport = PxSize::new(Px(100), Px(100));
5029        let mut scene = scene::FrameScene::default();
5030        scene.update(
5031            root,
5032            &tree.tree,
5033            &tree.metadatas,
5034            &root.descendants(&tree.tree).collect(),
5035            &HashSet::default(),
5036            viewport,
5037        );
5038        clones.set(0);
5039        tree.metadatas.geometry_changed.clear();
5040        let metadata = tree.metadatas.entry_or_default(outside);
5041        metadata.fragment = Default::default();
5042        metadata.fragment.push_draw_command(Marker {
5043            clones: outside_clones,
5044            value: 4,
5045            opacity: 1.0,
5046        });
5047        tree.metadatas.paint_changed.insert(outside);
5048        let (graph, _) = scene.update(
5049            root,
5050            &tree.tree,
5051            &tree.metadatas,
5052            &HashSet::default(),
5053            &HashSet::default(),
5054            viewport,
5055        );
5056        assert_eq!(
5057            clones.get(),
5058            0,
5059            "offscreen paint must not reassemble unchanged visible commands"
5060        );
5061        assert_eq!(
5062            graph
5063                .ops()
5064                .iter()
5065                .filter_map(|op| op.command.downcast_draw_ref::<Marker>())
5066                .map(|marker| marker.value)
5067                .collect::<Vec<_>>(),
5068            vec![1, 5]
5069        );
5070        tree.metadatas.paint_changed.clear();
5071        tree.metadatas.geometry_changed.insert(leaf);
5072        scene.update(
5073            root,
5074            &tree.tree,
5075            &tree.metadatas,
5076            &HashSet::default(),
5077            &HashSet::default(),
5078            viewport,
5079        );
5080        assert_eq!(
5081            clones.get(),
5082            0,
5083            "an unchanged draw in the geometry journal must retain its visible output"
5084        );
5085        for (offset, opacity) in [(1.25, 1.0), (2.5, 0.5), (3.75, 0.5)] {
5086            clones.set(0);
5087            tree.metadatas.geometry_changed.clear();
5088            tree.metadatas.mark_geometry_dirty(root);
5089            let metadata = tree.metadatas.entry_or_default(root);
5090            metadata.rel_position = Some(PxPosition::from_f32_xy(offset, 0.0));
5091            metadata.opacity = opacity;
5092            populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5093            assert!(
5094                !tree.metadatas.geometry_changed.contains(&root),
5095                "a passive layout must not publish geometry notifications for its own translation"
5096            );
5097            assert_eq!(
5098                tree.metadatas.get(&root).unwrap().abs_position,
5099                Some(PxPosition::from_f32_xy(offset, 0.0)),
5100                "passive layout coordinates must remain available for inspection"
5101            );
5102            assert!(tree.metadatas.geometry_changed.contains(&leaf));
5103            let (graph, _) = scene.update(
5104                root,
5105                &tree.tree,
5106                &tree.metadatas,
5107                &HashSet::default(),
5108                &HashSet::default(),
5109                viewport,
5110            );
5111            assert_eq!(
5112                clones.get(),
5113                1,
5114                "translation must copy the command only into the frame output"
5115            );
5116            let op = graph
5117                .ops()
5118                .iter()
5119                .find(|op| op.command.downcast_draw_ref::<Marker>().is_some())
5120                .unwrap();
5121            let marker = op.command.downcast_draw_ref::<Marker>().unwrap();
5122            assert_eq!(marker.value, 1);
5123            assert_eq!(marker.opacity, opacity);
5124            assert_eq!(
5125                op.geometry.window_origin(),
5126                PxPosition::from_f32_xy(offset, 0.0)
5127            );
5128        }
5129        let metadata = tree.metadatas.entry_or_default(leaf);
5130        metadata.fragment = Default::default();
5131        metadata.fragment.push_draw_command(Marker {
5132            clones,
5133            value: 2,
5134            opacity: 1.0,
5135        });
5136        tree.metadatas.paint_changed.insert(leaf);
5137        let (graph, _) = scene.update(
5138            root,
5139            &tree.tree,
5140            &tree.metadatas,
5141            &HashSet::default(),
5142            &HashSet::default(),
5143            viewport,
5144        );
5145        let marker = graph
5146            .ops()
5147            .iter()
5148            .find_map(|op| op.command.downcast_draw_ref::<Marker>())
5149            .unwrap();
5150        assert_eq!(marker.value, 2);
5151        assert_eq!(marker.opacity, 0.5);
5152        tree.metadatas.paint_changed.clear();
5153        for (offset, width, visible) in [
5154            (150.0, 100, false),
5155            (160.0, 100, false),
5156            (160.0, 200, true),
5157            (160.0, 100, false),
5158            (0.25, 100, true),
5159        ] {
5160            tree.metadatas.geometry_changed.clear();
5161            tree.metadatas.mark_geometry_dirty(root);
5162            tree.metadatas.entry_or_default(root).rel_position =
5163                Some(PxPosition::from_f32_xy(offset, 0.0));
5164            populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5165            let (graph, _) = scene.update(
5166                root,
5167                &tree.tree,
5168                &tree.metadatas,
5169                &HashSet::default(),
5170                &HashSet::default(),
5171                PxSize::new(Px(width), Px(100)),
5172            );
5173            assert_eq!(
5174                graph
5175                    .ops()
5176                    .iter()
5177                    .any(|op| op.command.downcast_draw_ref::<Marker>().is_some()),
5178                visible,
5179                "retained visibility must follow translation and viewport resizing"
5180            );
5181        }
5182        tree.metadatas.geometry_changed.clear();
5183        tree.metadatas.mark_geometry_dirty(outside);
5184        tree.metadatas.entry_or_default(outside).rel_position =
5185            Some(PxPosition::from_f32_xy(40.0, 0.0));
5186        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5187        let (graph, _) = scene.update(
5188            root,
5189            &tree.tree,
5190            &tree.metadatas,
5191            &HashSet::default(),
5192            &HashSet::default(),
5193            viewport,
5194        );
5195        assert_eq!(
5196            graph
5197                .ops()
5198                .iter()
5199                .filter_map(|op| op.command.downcast_draw_ref::<Marker>())
5200                .map(|marker| marker.value)
5201                .collect::<Vec<_>>(),
5202            vec![2, 4, 5],
5203            "revealing an omitted branch must publish its latest paint in sibling order"
5204        );
5205    }
5206
5207    #[test]
5208    fn unplaced_layout_subtrees_hide_geometry_input_drawing_semantics_and_focus() {
5209        let _runtime_session = crate::RuntimeSession::new();
5210        let _runtime_scope = _runtime_session.enter();
5211        use crate::focus::{FocusNode, FocusRegistration};
5212
5213        #[derive(Clone)]
5214        struct Marker;
5215
5216        impl crate::DrawCommand for Marker {
5217            fn apply_opacity(&mut self, _opacity: f32) {}
5218        }
5219
5220        let mut tree = ComponentTree::new();
5221        let root = tree.add_node(node("root", 1, 1));
5222        tree.add_node(node_with_role("transparent", NodeRole::Composition, 2, 2));
5223        let panel = tree.add_node(node("panel", 3, 3));
5224        tree.add_node(node_with_role("content", NodeRole::Composition, 4, 4));
5225        let leaf_focus = FocusRegistration::target(FocusNode::new());
5226        let mut leaf_node = node("leaf", 5, 5);
5227        leaf_node.interaction_mut().focus_registration = Some(leaf_focus);
5228        let leaf = tree.add_node(leaf_node);
5229        for _ in 0..4 {
5230            tree.pop_node();
5231        }
5232        let outside_focus = FocusRegistration::target(FocusNode::new());
5233        let mut outside_node = node("outside", 6, 6);
5234        outside_node.interaction_mut().focus_registration = Some(outside_focus);
5235        let outside = tree.add_node(outside_node);
5236        tree.pop_node();
5237        tree.pop_node();
5238        for (id, length) in [(root, 200), (panel, 30), (leaf, 6), (outside, 6)] {
5239            tree.metadatas.entry_or_default(id).computed_data = Some(ComputedData {
5240                width: Px(length),
5241                height: Px(length),
5242            });
5243        }
5244        tree.metadatas.entry_or_default(leaf).rel_position = Some(PxPosition::new(Px(4), Px(5)));
5245        tree.metadatas.entry_or_default(outside).rel_position =
5246            Some(PxPosition::new(Px(240), Px(240)));
5247        tree.metadatas.entry_or_default(root).clips_children = true;
5248        tree.metadatas.entry_or_default(leaf).set_accessibility(
5249            Some(
5250                crate::accessibility::AccessibilityNode::new()
5251                    .with_role(accesskit::Role::Button)
5252                    .with_label("Panel action"),
5253            ),
5254            None,
5255        );
5256        for position in [Some(20), None, Some(40)] {
5257            tree.metadatas.mark_geometry_dirty(panel);
5258            tree.metadatas.entry_or_default(panel).rel_position =
5259                position.map(|position| PxPosition::new(Px(position), Px(position)));
5260            let metadata = tree.metadatas.entry_or_default(leaf);
5261            metadata.fragment = Default::default();
5262            metadata.fragment.push_draw_command(Marker);
5263            populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5264            assert_eq!(
5265                tree.metadatas.get(&outside).unwrap().event_clip_rect(),
5266                Some(PxRect::new(Px::ZERO, Px::ZERO, Px(200), Px(200))),
5267                "the placed traversal target is outside its ancestor's clip"
5268            );
5269            tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5270            tree.focus_owner
5271                .sync_layout_from_component_tree(&tree.tree, &tree.metadatas);
5272            if position.is_none() {
5273                assert_eq!(
5274                    tree.focus_owner.active_component_node_id(),
5275                    None,
5276                    "hiding the active subtree releases focus, including capture"
5277                );
5278            }
5279            tree.focus_owner.clear_focus_global(true);
5280            tree.focus_owner.request_focus(leaf_focus.id);
5281            tree.focus_owner.commit_pending();
5282            assert_eq!(
5283                tree.focus_owner.active_component_node_id(),
5284                position.map(|_| leaf),
5285                "unplaced descendants reject direct focus requests"
5286            );
5287            tree.focus_owner.clear_focus_global(true);
5288            assert!(tree.focus_owner.move_focus(FocusDirection::Next));
5289            assert_eq!(
5290                tree.focus_owner.active_component_node_id(),
5291                Some(if position.is_some() { leaf } else { outside }),
5292                "forward traversal skips the unplaced subtree"
5293            );
5294            if position.is_some() {
5295                assert!(tree.focus_owner.move_focus(FocusDirection::Next));
5296            }
5297            assert_eq!(
5298                tree.focus_owner.move_focus(FocusDirection::Previous),
5299                position.is_some(),
5300                "reverse traversal also skips the unplaced subtree"
5301            );
5302            assert_eq!(
5303                tree.focus_owner.active_component_node_id(),
5304                Some(if position.is_some() { leaf } else { outside })
5305            );
5306            if position.is_some() {
5307                tree.focus_owner.capture_focus(leaf_focus.id);
5308            }
5309            let graph = build_render_graph(
5310                root,
5311                &tree.tree,
5312                &mut tree.metadatas,
5313                PxSize::new(Px(200), Px(200)),
5314            );
5315            let expected =
5316                position.map(|position| PxPosition::new(Px(position + 4), Px(position + 5)));
5317            assert_eq!(tree.metadatas.get(&leaf).unwrap().abs_position, expected);
5318            assert_eq!(
5319                tree.metadatas.get(&leaf).unwrap().base_abs_position,
5320                expected
5321            );
5322            assert_eq!(
5323                resolve_node_input_context(&tree.tree, &tree.metadatas, leaf).is_some(),
5324                expected.is_some()
5325            );
5326            let marker_ops: Vec<_> = graph
5327                .ops()
5328                .iter()
5329                .filter(|op| op.type_id == std::any::TypeId::of::<Marker>())
5330                .collect();
5331            assert_eq!(marker_ops.len(), usize::from(expected.is_some()));
5332            let accessibility = crate::accessibility::build_tree_update(
5333                &tree.tree,
5334                &tree.metadatas,
5335                root,
5336                None,
5337                Some(leaf),
5338            )
5339            .expect("accessibility tree");
5340            assert_eq!(
5341                accessibility
5342                    .nodes
5343                    .iter()
5344                    .any(|(_, node)| node.label() == Some("Panel action")),
5345                expected.is_some()
5346            );
5347            assert_eq!(
5348                accessibility
5349                    .nodes
5350                    .iter()
5351                    .find(|(id, _)| *id == accessibility.focus)
5352                    .and_then(|(_, node)| node.label()),
5353                expected.map(|_| "Panel action")
5354            );
5355            if let Some(expected) = expected {
5356                assert_eq!(marker_ops[0].geometry.window_origin(), expected);
5357            } else {
5358                tree.metadatas.geometry_changed.clear();
5359                tree.metadatas.mark_geometry_dirty(root);
5360                tree.metadatas.entry_or_default(root).rel_position =
5361                    Some(PxPosition::from_f32_xy(0.25, 0.5));
5362                populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5363                assert!(
5364                    !tree.metadatas.geometry_changed.contains(&leaf),
5365                    "moving an ancestor must not invalidate an already hidden subtree"
5366                );
5367                assert!(tree.metadatas.get(&leaf).unwrap().abs_position.is_none());
5368                tree.metadatas.entry_or_default(root).rel_position = None;
5369                tree.metadatas.mark_geometry_dirty(root);
5370            }
5371        }
5372    }
5373
5374    #[test]
5375    fn pointer_samples_route_at_their_own_positions_and_complete_passes_in_order() {
5376        let _runtime_session = crate::RuntimeSession::new();
5377        let _runtime_scope = _runtime_session.enter();
5378        use crate::cursor::{GestureState, MOUSE_POINTER_ID, PressKeyEventType};
5379
5380        let log = Arc::new(std::sync::Mutex::new(Vec::new()));
5381        let mut tree = ComponentTree::new();
5382        let root = tree.add_node(node("root", 1, 1));
5383        let origin = Instant::now();
5384        for index in 0..2 {
5385            let mut target = node("target", index + 2, index + 2);
5386            for pass in [
5387                PointerEventPass::Initial,
5388                PointerEventPass::Main,
5389                PointerEventPass::Final,
5390            ] {
5391                let log = Arc::clone(&log);
5392                let handler: Box<PointerInputHandlerFn> = Box::new(move |input| {
5393                    for change in input.pointer_changes.iter() {
5394                        log.lock().unwrap().push((
5395                            index,
5396                            pass,
5397                            change.timestamp.duration_since(origin).as_nanos(),
5398                            input.cursor_position_rel,
5399                        ));
5400                    }
5401                });
5402                match pass {
5403                    PointerEventPass::Initial => target
5404                        .interaction_mut()
5405                        .pointer_preview_handlers
5406                        .push(handler),
5407                    PointerEventPass::Main => {
5408                        target.interaction_mut().pointer_handlers.push(handler)
5409                    }
5410                    PointerEventPass::Final => target
5411                        .interaction_mut()
5412                        .pointer_final_handlers
5413                        .push(handler),
5414                }
5415            }
5416            let id = tree.add_node(target);
5417            let metadata = tree.metadatas.get_mut(&id).unwrap();
5418            let position = PxPosition::new(Px(index as i32 * 200), Px::ZERO);
5419            metadata.base_abs_position = Some(position);
5420            metadata.abs_position = Some(position);
5421            metadata.computed_data = Some(ComputedData {
5422                width: Px(100),
5423                height: Px(100),
5424            });
5425            tree.pop_node();
5426        }
5427        tree.pop_node();
5428        let metadata = tree.metadatas.get_mut(&root).unwrap();
5429        metadata.base_abs_position = Some(PxPosition::ZERO);
5430        metadata.abs_position = Some(PxPosition::ZERO);
5431        metadata.computed_data = Some(ComputedData {
5432            width: Px(400),
5433            height: Px(100),
5434        });
5435        let left = PxPosition::new(Px(20), Px(20));
5436        let right = PxPosition::new(Px(220), Px(20));
5437        let outside = PxPosition::new(Px(500), Px(20));
5438        let mut events: Vec<_> = [
5439            (
5440                CursorEventContent::Pressed(PressKeyEventType::Left),
5441                Some(left),
5442            ),
5443            (
5444                CursorEventContent::Released(PressKeyEventType::Left),
5445                Some(left),
5446            ),
5447            (CursorEventContent::Moved(outside), Some(outside)),
5448            (
5449                CursorEventContent::Pressed(PressKeyEventType::Left),
5450                Some(right),
5451            ),
5452            (
5453                CursorEventContent::Released(PressKeyEventType::Left),
5454                Some(right),
5455            ),
5456            (CursorEventContent::Moved(outside), Some(outside)),
5457        ]
5458        .into_iter()
5459        .enumerate()
5460        .map(|(index, (content, position))| {
5461            PointerChange::new(
5462                origin + std::time::Duration::from_nanos(index as u64),
5463                MOUSE_POINTER_ID,
5464                content,
5465                GestureState::TapCandidate,
5466            )
5467            .with_position(position)
5468        })
5469        .collect();
5470        tree.dispatch_pointer_input(
5471            root,
5472            &mut events,
5473            &mut Some(outside),
5474            winit::keyboard::ModifiersState::empty(),
5475        );
5476        let expected: Vec<_> = [(0, 0), (0, 1), (1, 3), (1, 4)]
5477            .into_iter()
5478            .flat_map(|(index, sample)| {
5479                [
5480                    PointerEventPass::Initial,
5481                    PointerEventPass::Main,
5482                    PointerEventPass::Final,
5483                ]
5484                .map(|pass| (index, pass, sample, Some(left)))
5485            })
5486            .collect();
5487        assert_eq!(*log.lock().unwrap(), expected);
5488        assert!(tree.active_pointer_paths.is_empty());
5489        log.lock().unwrap().clear();
5490        let mut captured: Vec<_> = [
5491            (
5492                CursorEventContent::Pressed(PressKeyEventType::Left),
5493                Some(left),
5494            ),
5495            (CursorEventContent::Moved(outside), Some(outside)),
5496            (CursorEventContent::Released(PressKeyEventType::Left), None),
5497        ]
5498        .into_iter()
5499        .enumerate()
5500        .map(|(index, (content, position))| {
5501            PointerChange::new(
5502                origin + std::time::Duration::from_nanos(index as u64),
5503                MOUSE_POINTER_ID,
5504                content,
5505                GestureState::TapCandidate,
5506            )
5507            .with_position(position)
5508        })
5509        .collect();
5510        tree.dispatch_pointer_input(
5511            root,
5512            &mut captured,
5513            &mut Some(right),
5514            winit::keyboard::ModifiersState::empty(),
5515        );
5516        let expected: Vec<_> = [Some(left), Some(outside), None]
5517            .into_iter()
5518            .enumerate()
5519            .flat_map(|(sample, position)| {
5520                [
5521                    PointerEventPass::Initial,
5522                    PointerEventPass::Main,
5523                    PointerEventPass::Final,
5524                ]
5525                .map(|pass| (0, pass, sample as u128, position))
5526            })
5527            .collect();
5528        assert_eq!(
5529            *log.lock().unwrap(),
5530            expected,
5531            "capture delivers outside movement and an unavailable-position release to the original target"
5532        );
5533        assert!(tree.active_pointer_paths.is_empty());
5534
5535        let mut press = [PointerChange::new(
5536            origin,
5537            MOUSE_POINTER_ID,
5538            CursorEventContent::Pressed(PressKeyEventType::Left),
5539            GestureState::TapCandidate,
5540        )
5541        .with_position(Some(left))];
5542        tree.dispatch_pointer_input(
5543            root,
5544            &mut press,
5545            &mut Some(left),
5546            winit::keyboard::ModifiersState::empty(),
5547        );
5548        let target = tree.find_node_id_by_instance_key(2).unwrap();
5549        tree.metadatas.get_mut(&target).unwrap().event_clip_bounds = Some(crate::FloatRect::ZERO);
5550        tree.metadatas.geometry_changed.insert(target);
5551        log.lock().unwrap().clear();
5552        let mut release = [PointerChange::new(
5553            origin + std::time::Duration::from_nanos(1),
5554            MOUSE_POINTER_ID,
5555            CursorEventContent::Released(PressKeyEventType::Left),
5556            GestureState::TapCandidate,
5557        )
5558        .with_position(Some(left))];
5559        tree.dispatch_pointer_input(
5560            root,
5561            &mut release,
5562            &mut Some(left),
5563            winit::keyboard::ModifiersState::empty(),
5564        );
5565        assert_eq!(
5566            *log.lock().unwrap(),
5567            [
5568                PointerEventPass::Initial,
5569                PointerEventPass::Main,
5570                PointerEventPass::Final
5571            ]
5572            .map(|pass| (0, pass, 1, None)),
5573            "a captured target scrolled outside its clip must still receive release"
5574        );
5575        assert!(tree.active_pointer_paths.is_empty());
5576    }
5577
5578    #[test]
5579    fn recomposition_preserves_pointer_capture_but_runtime_reset_discards_it() {
5580        let _runtime_session = crate::RuntimeSession::new();
5581        let _runtime_scope = _runtime_session.enter();
5582        let mut tree = ComponentTree::new();
5583        tree.active_pointer_paths.insert(7, vec![1, 2]);
5584        tree.active_pointer_positions
5585            .insert(7, Some(PxPosition::ZERO));
5586        tree.clear();
5587        assert_eq!(tree.active_pointer_paths.get(&7), Some(&vec![1, 2]));
5588        assert_eq!(
5589            tree.active_pointer_positions.get(&7),
5590            Some(&Some(PxPosition::ZERO))
5591        );
5592        tree.reset();
5593        assert!(tree.active_pointer_paths.is_empty());
5594        assert!(tree.active_pointer_positions.is_empty());
5595    }
5596
5597    fn focus_targets(attach: bool) -> (ComponentTree, Vec<(NodeId, FocusHandleId)>) {
5598        use crate::focus::{FocusNode, FocusRegistration};
5599
5600        let mut tree = ComponentTree::new();
5601        let root = tree.add_node(node("root", 1, 1));
5602        let mut targets = Vec::new();
5603        for index in 0..2 {
5604            let registration = FocusRegistration::target(FocusNode::new());
5605            let mut target = node("target", index + 2, index + 2);
5606            target.interaction_mut().focus_registration = Some(registration);
5607            let node_id = tree.add_node(target);
5608            targets.push((node_id, registration.id));
5609            tree.pop_node();
5610        }
5611        tree.pop_node();
5612        if attach {
5613            tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5614        }
5615        (tree, targets)
5616    }
5617
5618    #[test]
5619    fn repeated_focus_request_does_not_replay_after_action_restores_another_target() {
5620        let _runtime_session = crate::RuntimeSession::new();
5621        let _runtime_scope = _runtime_session.enter();
5622        let (mut tree, targets) = focus_targets(true);
5623        let [(action, action_handle), (anchor, anchor_handle)] = targets.as_slice() else {
5624            unreachable!();
5625        };
5626        tree.focus_owner.request_focus(*action_handle);
5627        assert_eq!(tree.focus_owner.active_component_node_id(), Some(*action));
5628        tree.focus_owner.request_focus(*action_handle);
5629        tree.focus_owner.request_focus(*anchor_handle);
5630        tree.focus_owner.commit_pending();
5631        assert_eq!(tree.focus_owner.active_component_node_id(), Some(*anchor));
5632        tree.focus_owner.commit_pending();
5633        assert_eq!(tree.focus_owner.active_component_node_id(), Some(*anchor));
5634    }
5635
5636    #[test]
5637    fn clearing_an_unfocused_attached_target_does_not_clear_its_future_focus() {
5638        let _runtime_session = crate::RuntimeSession::new();
5639        let _runtime_scope = _runtime_session.enter();
5640        let (mut tree, targets) = focus_targets(true);
5641        tree.focus_owner.request_focus(targets[1].1);
5642        tree.focus_owner.clear_focus(targets[0].1);
5643        tree.focus_owner.request_focus(targets[0].1);
5644        tree.focus_owner.commit_pending();
5645        assert_eq!(
5646            tree.focus_owner.active_component_node_id(),
5647            Some(targets[0].0)
5648        );
5649    }
5650
5651    #[test]
5652    fn rejected_capture_does_not_capture_a_later_focus_request() {
5653        let _runtime_session = crate::RuntimeSession::new();
5654        let _runtime_scope = _runtime_session.enter();
5655        let (mut tree, targets) = focus_targets(true);
5656        tree.focus_owner.request_focus(targets[1].1);
5657        tree.focus_owner.capture_focus(targets[0].1);
5658        tree.focus_owner.request_focus(targets[0].1);
5659        tree.focus_owner.commit_pending();
5660        tree.focus_owner.request_focus(targets[1].1);
5661        tree.focus_owner.commit_pending();
5662        assert_eq!(
5663            tree.focus_owner.active_component_node_id(),
5664            Some(targets[1].0)
5665        );
5666    }
5667
5668    #[test]
5669    fn redundant_free_does_not_release_a_later_capture() {
5670        let _runtime_session = crate::RuntimeSession::new();
5671        let _runtime_scope = _runtime_session.enter();
5672        let (mut tree, targets) = focus_targets(true);
5673        tree.focus_owner.request_focus(targets[0].1);
5674        tree.focus_owner.free_focus(targets[0].1);
5675        tree.focus_owner.capture_focus(targets[0].1);
5676        tree.focus_owner.commit_pending();
5677        tree.focus_owner.request_focus(targets[1].1);
5678        tree.focus_owner.commit_pending();
5679        assert_eq!(
5680            tree.focus_owner.active_component_node_id(),
5681            Some(targets[0].0)
5682        );
5683    }
5684
5685    #[test]
5686    fn requests_before_attachment_are_consumed_once_the_target_is_attached() {
5687        let _runtime_session = crate::RuntimeSession::new();
5688        let _runtime_scope = _runtime_session.enter();
5689        let (mut tree, targets) = focus_targets(false);
5690        tree.focus_owner.request_focus(targets[0].1);
5691        tree.focus_owner.request_focus(targets[0].1);
5692        let root = tree.tree.get(targets[0].0).unwrap().parent().unwrap();
5693        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5694        tree.focus_owner.commit_pending();
5695        assert_eq!(
5696            tree.focus_owner.active_component_node_id(),
5697            Some(targets[0].0)
5698        );
5699        tree.focus_owner.request_focus(targets[1].1);
5700        tree.focus_owner.commit_pending();
5701        assert_eq!(
5702            tree.focus_owner.active_component_node_id(),
5703            Some(targets[1].0)
5704        );
5705    }
5706
5707    #[test]
5708    fn deferred_request_rejected_after_attachment_does_not_activate_on_enable() {
5709        let _runtime_session = crate::RuntimeSession::new();
5710        let _runtime_scope = _runtime_session.enter();
5711        let (mut tree, targets) = focus_targets(false);
5712        tree.focus_owner.request_focus(targets[0].1);
5713        let root = tree.tree.get(targets[0].0).unwrap().parent().unwrap();
5714        tree.tree
5715            .get_mut(targets[0].0)
5716            .unwrap()
5717            .get_mut()
5718            .interaction_mut()
5719            .focus_registration
5720            .as_mut()
5721            .unwrap()
5722            .properties
5723            .can_focus = false;
5724        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5725        tree.focus_owner.commit_pending();
5726        assert_eq!(tree.focus_owner.active_component_node_id(), None);
5727        tree.tree
5728            .get_mut(targets[0].0)
5729            .unwrap()
5730            .get_mut()
5731            .interaction_mut()
5732            .focus_registration
5733            .as_mut()
5734            .unwrap()
5735            .properties
5736            .can_focus = true;
5737        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5738        tree.focus_owner.commit_pending();
5739        assert_eq!(tree.focus_owner.active_component_node_id(), None);
5740    }
5741
5742    #[test]
5743    fn queued_navigation_routes_activation_to_the_new_focus_chain() {
5744        let _runtime_session = crate::RuntimeSession::new();
5745        let _runtime_scope = _runtime_session.enter();
5746        use crate::focus::{FocusNode, FocusRegistration};
5747
5748        let mut tree = ComponentTree::new();
5749        let root = tree.add_node(node("root", 1, 1));
5750        let mut trigger = node("trigger", 2, 2);
5751        trigger.interaction_mut().focus_registration =
5752            Some(FocusRegistration::target(FocusNode::new()));
5753        let trigger = tree.add_node(trigger);
5754        tree.pop_node();
5755        let popup = tree.add_node(node("popup", 3, 3));
5756        let mut action = node("action", 4, 4);
5757        action.interaction_mut().focus_registration =
5758            Some(FocusRegistration::target(FocusNode::new()));
5759        let action = tree.add_node(action);
5760        tree.pop_node();
5761        tree.pop_node();
5762        tree.pop_node();
5763        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5764        assert!(tree.focus_owner.move_focus(FocusDirection::Next));
5765        let mut events = VecDeque::from(["Tab", "Enter", "Shift+Tab", "Enter"]);
5766        let mut activations = Vec::new();
5767        let retry = drain_pending_focused_input(&mut events, |event| {
5768            match event {
5769                "Tab" => assert!(tree.focus_owner.move_focus(FocusDirection::Next)),
5770                "Shift+Tab" => assert!(tree.focus_owner.move_focus(FocusDirection::Previous)),
5771                "Enter" => activations.push(collect_focus_chain_node_ids(
5772                    root,
5773                    &tree.tree,
5774                    &tree.focus_owner,
5775                )),
5776                _ => unreachable!(),
5777            }
5778            tree.focus_owner.commit_pending();
5779            (None, false)
5780        });
5781        assert_eq!(retry, None);
5782        assert!(events.is_empty());
5783        assert_eq!(
5784            activations,
5785            [vec![root, popup, action], vec![root, trigger]]
5786        );
5787    }
5788
5789    #[test]
5790    fn skipped_focus_targets_anchor_navigation_without_becoming_destinations() {
5791        let _runtime_session = crate::RuntimeSession::new();
5792        let _runtime_scope = _runtime_session.enter();
5793        use crate::focus::{FocusNode, FocusRegistration, FocusScopeNode};
5794
5795        let mut tree = ComponentTree::new();
5796        let root = tree.add_node(node("root", 1, 1));
5797        let scope = FocusRegistration::scope(FocusScopeNode::new());
5798        let mut container = node("scope", 2, 2);
5799        container.interaction_mut().focus_registration = Some(scope);
5800        tree.add_node(container);
5801        let mut targets = Vec::new();
5802        for index in 0..3 {
5803            let mut registration = FocusRegistration::target(FocusNode::new());
5804            registration.properties.skip_traversal = index == 1;
5805            let mut target = node("target", index + 3, index + 3);
5806            target.interaction_mut().focus_registration = Some(registration);
5807            targets.push((tree.add_node(target), registration.id));
5808            tree.pop_node();
5809        }
5810        tree.pop_node();
5811        tree.pop_node();
5812        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5813        for scoped in [false, true] {
5814            for (direction, expected) in [(FocusDirection::Next, 2), (FocusDirection::Previous, 0)]
5815            {
5816                tree.focus_owner.request_focus(targets[1].1);
5817                assert_eq!(
5818                    tree.focus_owner.active_component_node_id(),
5819                    Some(targets[1].0)
5820                );
5821                assert!(if scoped {
5822                    tree.focus_owner
5823                        .move_focus_in_scope(scope.id, direction, true)
5824                } else {
5825                    tree.focus_owner.move_focus(direction)
5826                });
5827                assert_eq!(
5828                    tree.focus_owner.active_component_node_id(),
5829                    Some(targets[expected].0)
5830                );
5831            }
5832        }
5833        tree.focus_owner.request_focus(targets[0].1);
5834        assert!(tree.focus_owner.move_focus(FocusDirection::Next));
5835        assert_eq!(
5836            tree.focus_owner.active_component_node_id(),
5837            Some(targets[2].0)
5838        );
5839        for index in [0, 2] {
5840            tree.tree[targets[index].0]
5841                .get_mut()
5842                .interaction_mut()
5843                .focus_registration
5844                .as_mut()
5845                .unwrap()
5846                .properties
5847                .can_focus = false;
5848        }
5849        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5850        tree.focus_owner.request_focus(targets[1].1);
5851        for direction in [FocusDirection::Next, FocusDirection::Previous] {
5852            assert!(
5853                !tree
5854                    .focus_owner
5855                    .move_focus_in_scope(scope.id, direction, true)
5856            );
5857        }
5858    }
5859
5860    #[test]
5861    fn removing_unscoped_focus_does_not_activate_an_unrelated_control() {
5862        let _runtime_session = crate::RuntimeSession::new();
5863        let _runtime_scope = _runtime_session.enter();
5864        let (mut tree, targets) = focus_targets(true);
5865        tree.focus_owner.request_focus(targets[0].1);
5866        assert_eq!(
5867            tree.focus_owner.active_component_node_id(),
5868            Some(targets[0].0)
5869        );
5870        tree.focus_owner
5871            .remove_handles(&[targets[0].1].into_iter().collect(), &HashSet::default());
5872        assert_eq!(tree.focus_owner.active_component_node_id(), None);
5873    }
5874
5875    #[test]
5876    fn removing_focused_handles_restores_to_a_surviving_scope_target() {
5877        let _runtime_session = crate::RuntimeSession::new();
5878        let _runtime_scope = _runtime_session.enter();
5879        use crate::focus::{FocusNode, FocusRegistration, FocusRequester, FocusScopeNode};
5880
5881        let mut tree = ComponentTree::new();
5882        let root = tree.add_node(node("root", 1, 1));
5883        let scope = FocusRegistration::scope(FocusScopeNode::new());
5884        let fallback = FocusRequester::new();
5885        let mut container = node("scope", 2, 2);
5886        container.interaction_mut().focus_registration = Some(scope);
5887        container.interaction_mut().focus_restorer_fallback = Some(fallback);
5888        tree.add_node(container);
5889        let removed = FocusRegistration::target(FocusNode::new());
5890        let mut target = node("removed", 3, 3);
5891        target.interaction_mut().focus_registration = Some(removed);
5892        let removed_node = tree.add_node(target);
5893        tree.pop_node();
5894        let mut target = node("survivor", 4, 4);
5895        target.interaction_mut().focus_registration =
5896            Some(FocusRegistration::target(FocusNode::new()));
5897        target.interaction_mut().focus_requester_binding = Some(fallback);
5898        let survivor = tree.add_node(target);
5899        tree.pop_node();
5900        tree.pop_node();
5901        tree.pop_node();
5902        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5903        tree.focus_owner.request_focus(removed.id);
5904        assert_eq!(
5905            tree.focus_owner.active_component_node_id(),
5906            Some(removed_node)
5907        );
5908        tree.focus_owner
5909            .remove_handles(&[removed.id].into_iter().collect(), &HashSet::default());
5910        assert_eq!(tree.focus_owner.active_component_node_id(), Some(survivor));
5911    }
5912
5913    #[test]
5914    fn detached_focus_scopes_retain_their_target_through_layout_until_reattached() {
5915        let _runtime_session = crate::RuntimeSession::new();
5916        let _runtime_scope = _runtime_session.enter();
5917        use crate::focus::{FocusNode, FocusRegistration, FocusScopeNode};
5918
5919        let mut tree = ComponentTree::new();
5920        let root = tree.add_node(node("root", 1, 1));
5921        let mut container = node_with_role("scope", NodeRole::Composition, 2, 2);
5922        container.interaction_mut().focus_registration =
5923            Some(FocusRegistration::scope(FocusScopeNode::new()));
5924        let scope = tree.add_node(container);
5925        let registration = FocusRegistration::target(FocusNode::new());
5926        let mut target = node("target", 3, 3);
5927        target.interaction_mut().focus_registration = Some(registration);
5928        let target = tree.add_node(target);
5929        for _ in 0..3 {
5930            tree.pop_node();
5931        }
5932        for id in [root, target] {
5933            let metadata = tree.metadatas.entry_or_default(id);
5934            metadata.computed_data = Some(ComputedData {
5935                width: Px(20),
5936                height: Px(20),
5937            });
5938            metadata.rel_position = Some(PxPosition::ZERO);
5939        }
5940        populate_layout_metadata(root, &tree.tree, &mut tree.metadatas);
5941        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5942        tree.focus_owner
5943            .sync_layout_from_component_tree(&tree.tree, &tree.metadatas);
5944        tree.focus_owner.request_focus(registration.id);
5945        assert_eq!(tree.focus_owner.active_component_node_id(), Some(target));
5946
5947        scope.detach(&mut tree.tree);
5948        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5949        tree.focus_owner
5950            .sync_layout_from_component_tree(&tree.tree, &tree.metadatas);
5951        assert_eq!(tree.focus_owner.active_component_node_id(), None);
5952        root.append(scope, &mut tree.tree);
5953        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5954        tree.focus_owner
5955            .sync_layout_from_component_tree(&tree.tree, &tree.metadatas);
5956        assert_eq!(tree.focus_owner.active_component_node_id(), Some(target));
5957    }
5958
5959    #[test]
5960    fn redundant_scope_restore_does_not_pull_focus_back_from_another_scope() {
5961        let _runtime_session = crate::RuntimeSession::new();
5962        let _runtime_scope = _runtime_session.enter();
5963        use crate::focus::{FocusNode, FocusRegistration, FocusScopeNode};
5964
5965        let mut tree = ComponentTree::new();
5966        let root = tree.add_node(node("root", 1, 1));
5967        let scope = FocusRegistration::scope(FocusScopeNode::new());
5968        let mut container = node("scope", 2, 2);
5969        container.interaction_mut().focus_registration = Some(scope);
5970        tree.add_node(container);
5971        let inside = FocusRegistration::target(FocusNode::new());
5972        let mut target = node("inside", 3, 3);
5973        target.interaction_mut().focus_registration = Some(inside);
5974        tree.add_node(target);
5975        tree.pop_node();
5976        tree.pop_node();
5977        let outside = FocusRegistration::target(FocusNode::new());
5978        let mut target = node("outside", 4, 4);
5979        target.interaction_mut().focus_registration = Some(outside);
5980        let outside_node = tree.add_node(target);
5981        tree.pop_node();
5982        tree.pop_node();
5983        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
5984        tree.focus_owner.request_focus(inside.id);
5985        tree.focus_owner.restore_focus(scope.id);
5986        tree.focus_owner.request_focus(outside.id);
5987        tree.focus_owner.commit_pending();
5988        assert_eq!(
5989            tree.focus_owner.active_component_node_id(),
5990            Some(outside_node)
5991        );
5992    }
5993
5994    #[test]
5995    fn focus_retry_keeps_later_keys_before_newly_received_input() {
5996        let _runtime_session = crate::RuntimeSession::new();
5997        let _runtime_scope = _runtime_session.enter();
5998        let mut events = VecDeque::from(["Tab into lazy content", "Enter", "Shift+Tab"]);
5999        let mut dispatched = Vec::new();
6000        let retry = drain_pending_focused_input(&mut events, |event| {
6001            dispatched.push(event);
6002            (Some(FocusDirection::Next), false)
6003        });
6004        assert_eq!(retry, Some(FocusDirection::Next));
6005        assert_eq!(dispatched, ["Tab into lazy content"]);
6006        events.push_back("new input");
6007        assert_eq!(
6008            drain_pending_focused_input(&mut events, |event| {
6009                dispatched.push(event);
6010                (None, false)
6011            }),
6012            None
6013        );
6014        assert_eq!(
6015            dispatched,
6016            ["Tab into lazy content", "Enter", "Shift+Tab", "new input"]
6017        );
6018        assert!(events.is_empty());
6019    }
6020
6021    #[test]
6022    fn keyboard_barrier_preserves_order_until_a_subsequent_layout_finishes() {
6023        let _runtime_session = crate::RuntimeSession::new();
6024        let _runtime_scope = _runtime_session.enter();
6025        use crate::focus::{FocusManager, bind_focus_owner};
6026
6027        let mut owner = FocusOwner::new();
6028        let mut events = VecDeque::from(["close popup", "Enter", "Tab"]);
6029        let mut delivered = Vec::new();
6030        assert_eq!(
6031            drain_pending_focused_input(&mut events, |event| {
6032                delivered.push(event);
6033                {
6034                    let _binding = bind_focus_owner(&mut owner);
6035                    FocusManager::current().defer_keyboard_until_layout();
6036                }
6037                (None, owner.keyboard_is_deferred())
6038            }),
6039            None
6040        );
6041        assert_eq!(delivered, ["close popup"]);
6042        assert_eq!(events, ["Enter", "Tab"]);
6043        owner.finish_layout();
6044        assert!(
6045            owner.keyboard_is_deferred(),
6046            "the old layout cannot release the barrier"
6047        );
6048        owner.begin_layout();
6049        assert!(
6050            owner.keyboard_is_deferred(),
6051            "focus callbacks still have to finish"
6052        );
6053        {
6054            let _binding = bind_focus_owner(&mut owner);
6055            FocusManager::current().defer_keyboard_until_layout();
6056        }
6057        owner.finish_layout();
6058        assert!(
6059            owner.keyboard_is_deferred(),
6060            "a new transition survives the old layout callback"
6061        );
6062        owner.begin_layout();
6063        owner.finish_layout();
6064        assert!(!owner.keyboard_is_deferred());
6065        events.push_back("new input");
6066        drain_pending_focused_input(&mut events, |event| {
6067            delivered.push(event);
6068            (None, owner.keyboard_is_deferred())
6069        });
6070        assert_eq!(delivered, ["close popup", "Enter", "Tab", "new input"]);
6071        assert!(events.is_empty());
6072    }
6073
6074    #[test]
6075    fn recomposition_retains_deferred_input_but_runtime_reset_discards_it() {
6076        let _runtime_session = crate::RuntimeSession::new();
6077        let _runtime_scope = _runtime_session.enter();
6078        use crate::focus::{FocusManager, bind_focus_owner};
6079
6080        let mut tree = ComponentTree::new();
6081        {
6082            let _binding = bind_focus_owner(&mut tree.focus_owner);
6083            FocusManager::current().defer_keyboard_until_layout();
6084            assert!(FocusManager::current().request_move_focus(FocusDirection::Previous));
6085            assert!(FocusManager::current().request_move_focus(FocusDirection::Next));
6086        }
6087        assert_eq!(
6088            tree.focus_owner.take_pending_navigation(),
6089            Some(FocusDirection::Next)
6090        );
6091        tree.focus_owner
6092            .retain_navigation_retry(Some(FocusDirection::Next));
6093        let mut ime = crate::ime_state::ImeState::default();
6094        ime.push_event_for(None, winit::event::Ime::Commit("pending text".into()));
6095        tree.queue_focused_input(Vec::new(), ime.take_events());
6096        clipboard::request_clipboard_action(
6097            crate::clipboard::ClipboardAction::Paste("pending paste".into()),
6098            |_| panic!("reset clipboard request must not execute"),
6099        );
6100        tree.pending_focused_input.extend(
6101            clipboard::take_requests()
6102                .into_iter()
6103                .map(QueuedFocusedInput::Clipboard),
6104        );
6105        tree.clear();
6106        assert_eq!(tree.pending_focused_input.len(), 2);
6107        assert!(tree.focus_owner.has_pending_navigation());
6108        assert!(tree.focus_owner.keyboard_is_deferred());
6109        assert!(matches!(
6110            tree.pending_focused_input.front(),
6111            Some(QueuedFocusedInput::Ime(QueuedInput { event: (None, winit::event::Ime::Commit(text)), .. })) if text == "pending text"
6112        ));
6113        tree.focus_owner.set_owner_focused(false);
6114        assert!(!tree.focus_owner.has_pending_navigation());
6115        {
6116            let _binding = bind_focus_owner(&mut tree.focus_owner);
6117            assert!(!FocusManager::current().request_move_focus(FocusDirection::Previous));
6118        }
6119        tree.focus_owner.set_owner_focused(true);
6120        {
6121            let _binding = bind_focus_owner(&mut tree.focus_owner);
6122            assert!(FocusManager::current().request_move_focus(FocusDirection::Previous));
6123        }
6124        tree.reset();
6125        assert!(!tree.focus_owner.has_pending_navigation());
6126        assert!(!tree.focus_owner.keyboard_is_deferred());
6127        assert!(tree.pending_focused_input.is_empty());
6128    }
6129
6130    #[test]
6131    fn ime_commit_is_delivered_once_to_its_focused_client() {
6132        let _runtime_session = crate::RuntimeSession::new();
6133        let _runtime_scope = _runtime_session.enter();
6134        use crate::focus::{FocusNode, FocusRegistration};
6135
6136        let delivered = Arc::new(std::sync::Mutex::new(Vec::new()));
6137        let mut tree = ComponentTree::new();
6138        let root = tree.add_node(node("root", 1, 1));
6139        for index in 0..2 {
6140            let mut input = node("input", index + 2, index + 2);
6141            input.interaction_mut().focus_registration =
6142                Some(FocusRegistration::target(FocusNode::new()));
6143            let keys = Arc::clone(&delivered);
6144            input
6145                .interaction_mut()
6146                .keyboard_handlers
6147                .push(Box::new(move |input| {
6148                    for _ in input.keyboard_events.drain(..) {
6149                        keys.lock().unwrap().push((index, "key".into()));
6150                    }
6151                }));
6152            let delivered = Arc::clone(&delivered);
6153            input
6154                .interaction_mut()
6155                .ime_handlers
6156                .push(Box::new(move |input| {
6157                    for event in input.ime_events.drain(..) {
6158                        if let winit::event::Ime::Commit(text) = event {
6159                            delivered.lock().unwrap().push((index, text));
6160                        }
6161                    }
6162                }));
6163            tree.add_node(input);
6164            if index == 1 {
6165                let mut content = node("content", 10, 10);
6166                content
6167                    .interaction_mut()
6168                    .pointer_handlers
6169                    .push(Box::new(|mut input| {
6170                        input
6171                            .ime_session()
6172                            .show(ImeRequest::new(PxSize::new(Px(1), Px(1))));
6173                    }));
6174                tree.add_node(content);
6175                tree.pop_node();
6176            }
6177            tree.pop_node();
6178        }
6179        tree.pop_node();
6180        for id in root.descendants(&tree.tree) {
6181            let metadata = tree.metadatas.get_mut(&id).unwrap();
6182            metadata.base_abs_position = Some(PxPosition::ZERO);
6183            metadata.abs_position = Some(PxPosition::ZERO);
6184            metadata.computed_data = Some(ComputedData {
6185                width: Px(100),
6186                height: Px(40),
6187            });
6188        }
6189        tree.focus_owner.sync_from_component_tree(root, &tree.tree);
6190        assert!(tree.focus_owner.move_focus(FocusDirection::Next));
6191        let stale_client = tree.focus_owner.active_handle_id();
6192        assert!(tree.focus_owner.move_focus(FocusDirection::Next));
6193        let requests = tree.dispatch_pointer_input(
6194            root,
6195            &mut [],
6196            &mut Some(PxPosition::new(Px(5), Px(5))),
6197            winit::keyboard::ModifiersState::empty(),
6198        );
6199        let request = requests.ime_request.unwrap();
6200        assert_eq!(request.client_id, tree.focus_owner.active_handle_id());
6201        assert!(request.show_keyboard);
6202        let mut events = vec![
6203            (stale_client, winit::event::Ime::Commit("stale text".into())),
6204            (
6205                tree.focus_owner.active_handle_id(),
6206                winit::event::Ime::Commit("composed text".into()),
6207            ),
6208        ];
6209        let mut requests = WindowRequests::default();
6210        tree.dispatch_ime_input(root, &mut events, &mut requests);
6211        tree.dispatch_ime_input(root, &mut events, &mut requests);
6212        assert_eq!(*delivered.lock().unwrap(), [(1, "composed text".into())]);
6213        let mut ime = crate::ime_state::ImeState::default();
6214        let mut keyboard = crate::keyboard_state::KeyboardState::default();
6215        ime.push_event_for(None, winit::event::Ime::Commit("before".into()));
6216        keyboard.push_event(crate::KeyboardEvent::named(
6217            winit::keyboard::NamedKey::Backspace,
6218            winit::event::ElementState::Pressed,
6219        ));
6220        ime.push_event_for(None, winit::event::Ime::Commit("after".into()));
6221        tree.flush_focused_input(
6222            &mut crate::cursor::CursorState::default(),
6223            winit::keyboard::ModifiersState::empty(),
6224            keyboard.take_events(),
6225            ime.take_events(),
6226        );
6227        assert_eq!(
6228            &delivered.lock().unwrap()[1..],
6229            [(1, "before".into()), (1, "key".into()), (1, "after".into())]
6230        );
6231        let inputs: Vec<_> = root.children(&tree.tree).collect();
6232        tree.tree
6233            .get_mut(inputs[0])
6234            .unwrap()
6235            .get_mut()
6236            .interaction_mut()
6237            .ime_handlers = vec![Box::new(|mut input| {
6238            assert!(crate::FocusManager::current().move_focus(FocusDirection::Next));
6239            input
6240                .ime_session()
6241                .update(ImeRequest::new(PxSize::new(Px(1), Px(1))));
6242            input.block_ime();
6243        })];
6244        tree.tree
6245            .get_mut(inputs[1])
6246            .unwrap()
6247            .get_mut()
6248            .interaction_mut()
6249            .ime_handlers
6250            .push(Box::new(|mut input| {
6251                input
6252                    .ime_session()
6253                    .update(ImeRequest::new(PxSize::new(Px(1), Px(1))));
6254            }));
6255        assert!(tree.focus_owner.move_focus(FocusDirection::Previous));
6256        events.push((
6257            tree.focus_owner.active_handle_id(),
6258            winit::event::Ime::Commit("switch".into()),
6259        ));
6260        tree.dispatch_ime_input(root, &mut events, &mut requests);
6261        assert!(requests.ime_request.is_none());
6262        events.push((
6263            tree.focus_owner.active_handle_id(),
6264            winit::event::Ime::Commit("next text".into()),
6265        ));
6266        tree.dispatch_ime_input(root, &mut events, &mut requests);
6267        assert_eq!(
6268            requests.ime_request.unwrap().client_id,
6269            tree.focus_owner.active_handle_id()
6270        );
6271        assert_eq!(
6272            delivered.lock().unwrap().last(),
6273            Some(&(1, "next text".into()))
6274        );
6275        requests = WindowRequests::default();
6276        tree.dispatch_ime_input(root, &mut events, &mut requests);
6277        assert_eq!(
6278            requests.ime_request.unwrap().client_id,
6279            tree.focus_owner.active_handle_id()
6280        );
6281        for (source, expected) in [
6282            (stale_client, winit::event::ImeChangeCause::Other),
6283            (
6284                tree.focus_owner.active_handle_id(),
6285                winit::event::ImeChangeCause::InputMethod,
6286            ),
6287        ] {
6288            let mut ime = crate::ime_state::ImeState::default();
6289            ime.push_event_for(source, winit::event::Ime::Commit("source".into()));
6290            let requests = tree.flush_focused_input(
6291                &mut crate::cursor::CursorState::default(),
6292                winit::keyboard::ModifiersState::empty(),
6293                Vec::new(),
6294                ime.take_events(),
6295            );
6296            assert_eq!(requests.ime_request.unwrap().change_cause, expected);
6297        }
6298        tree.tree
6299            .get_mut(inputs[0])
6300            .unwrap()
6301            .get_mut()
6302            .interaction_mut()
6303            .ime_handlers = vec![Box::new(|mut input| {
6304            input
6305                .ime_session()
6306                .update(ImeRequest::new(PxSize::new(Px(1), Px(1))));
6307        })];
6308        tree.tree
6309            .get_mut(root)
6310            .unwrap()
6311            .get_mut()
6312            .interaction_mut()
6313            .ime_preview_handlers = vec![Box::new(|_| {
6314            assert!(crate::FocusManager::current().move_focus(FocusDirection::Next));
6315        })];
6316        assert!(tree.focus_owner.move_focus(FocusDirection::Previous));
6317        requests = WindowRequests::default();
6318        tree.dispatch_ime_input(root, &mut events, &mut requests);
6319        assert!(requests.ime_request.is_none());
6320    }
6321
6322    #[test]
6323    fn begin_replace_subtree_rejects_root_instance_key() {
6324        let _runtime_session = crate::RuntimeSession::new();
6325        let _runtime_scope = _runtime_session.enter();
6326        let mut tree = ComponentTree::new();
6327
6328        let root = tree.add_node(node("root", 1, 1));
6329        tree.pop_node();
6330
6331        let result = tree.begin_replace_subtree_by_instance_key(1);
6332        assert!(matches!(
6333            result,
6334            Err(ReplayReplaceError::RootNodeNotReplaceable)
6335        ));
6336        assert!(tree.get(root).is_some());
6337    }
6338
6339    #[test]
6340    fn finish_replace_subtree_keeps_inserted_roots_before_next_sibling() {
6341        let _runtime_session = crate::RuntimeSession::new();
6342        let _runtime_scope = _runtime_session.enter();
6343        let mut tree = ComponentTree::new();
6344
6345        let root = tree.add_node(node("root", 1, 1));
6346
6347        let _first = tree.add_node(node("first", 2, 2));
6348        let _first_child = tree.add_node(node("first_child", 3, 3));
6349        tree.pop_node();
6350        tree.pop_node();
6351
6352        let second = tree.add_node(node("second", 4, 4));
6353
6354        let context = match tree.begin_replace_subtree_by_instance_key(2) {
6355            Ok(context) => context,
6356            Err(_) => panic!("replace context should be created"),
6357        };
6358
6359        let new_a = tree.add_node(node("new_a", 2, 5));
6360        let _new_a_child = tree.add_node(node("new_a_child", 60, 6));
6361        tree.pop_node();
6362        tree.pop_node();
6363        let new_b = tree.add_node(node("new_b", 70, 7));
6364        tree.pop_node();
6365
6366        let mut replace_result = ReplayReplaceResult::default();
6367        assert!(
6368            tree.finish_replace_subtree(context, &mut replace_result)
6369                .is_ok()
6370        );
6371        assert_eq!(tree.current_build_node_id(), Some(second));
6372        tree.pop_node();
6373        tree.pop_node();
6374        tree.finish_replay_round();
6375
6376        let root_children = root.children(tree.tree()).collect::<Vec<_>>();
6377        assert_eq!(root_children, vec![new_a, new_b, second]);
6378        #[cfg(feature = "profiling")]
6379        assert_eq!(tree.get(root).unwrap().subtree_len, 5);
6380
6381        assert!(replace_result.removed_instance_keys.contains(&2));
6382        assert!(replace_result.removed_instance_keys.contains(&3));
6383        assert_eq!(
6384            replace_result.executed_keys().collect::<HashSet<_>>(),
6385            [5, 6, 7].into_iter().collect()
6386        );
6387        assert_eq!(
6388            replace_result.removed_instance_logic_ids,
6389            [3].into_iter().collect()
6390        );
6391        assert_eq!(
6392            replace_result
6393                .recomposed_logic_ids()
6394                .collect::<HashSet<_>>(),
6395            [2, 60, 70].into_iter().collect()
6396        );
6397
6398        assert!(tree.get(second).is_some());
6399    }
6400
6401    #[test]
6402    fn finish_replace_subtree_keeps_reused_subtree_and_excludes_it_from_removed_sets() {
6403        let _runtime_session = crate::RuntimeSession::new();
6404        let _runtime_scope = _runtime_session.enter();
6405        let mut tree = ComponentTree::new();
6406
6407        let root = tree.add_node(node("root", 1, 1));
6408
6409        let _first = tree.add_node(node("first", 2, 2));
6410        let _first_child = tree.add_node(node("first_child", 3, 3));
6411        tree.pop_node();
6412        tree.pop_node();
6413
6414        let second = tree.add_node(node("second", 4, 4));
6415        tree.pop_node();
6416        tree.pop_node();
6417
6418        let context = match tree.begin_replace_subtree_by_instance_key(2) {
6419            Ok(context) => context,
6420            Err(_) => panic!("replace context should be created"),
6421        };
6422
6423        assert_eq!(tree.attach_retained_subtree(2, 2), Some(_first));
6424        tree.pop_node();
6425
6426        let mut replace_result = ReplayReplaceResult::default();
6427        assert!(
6428            tree.finish_replace_subtree(context, &mut replace_result)
6429                .is_ok()
6430        );
6431        tree.finish_replay_round();
6432
6433        let root_children = root
6434            .children(tree.tree())
6435            .map(|id| tree.get(id).expect("child must exist").fn_name)
6436            .collect::<Vec<_>>();
6437        assert_eq!(root_children, vec!["first", "second"]);
6438        #[cfg(feature = "profiling")]
6439        assert_eq!(tree.get(root).unwrap().subtree_len, 4);
6440        assert!(tree.find_node_id_by_instance_key(3).is_some());
6441        assert!(tree.get(second).is_some());
6442
6443        assert_eq!(replace_result.executed_keys().next(), None);
6444        assert_eq!(tree.find_node_id_by_instance_key(3), Some(_first_child));
6445        assert_eq!(replace_result.recomposed_logic_ids().next(), None);
6446        assert!(tree.removed_frame_nodes.is_empty());
6447        assert!(!replace_result.removed_instance_keys.contains(&2));
6448        assert!(!replace_result.removed_instance_keys.contains(&3));
6449        assert!(!replace_result.removed_instance_logic_ids.contains(&2));
6450        assert!(!replace_result.removed_instance_logic_ids.contains(&3));
6451
6452        let Ok(context) = tree.begin_replace_subtree_by_instance_key(4) else {
6453            panic!("second replace context should be created");
6454        };
6455        tree.add_node(node("new_second", 5, 5));
6456        tree.pop_node();
6457        assert!(
6458            tree.finish_replace_subtree(context, &mut replace_result)
6459                .is_ok()
6460        );
6461        assert!(replace_result.executed_keys().eq([5]));
6462        assert!(replace_result.recomposed_logic_ids().eq([5]));
6463        assert_eq!(
6464            replace_result.removed_instance_keys,
6465            [4].into_iter().collect()
6466        );
6467    }
6468
6469    #[test]
6470    fn replay_preserves_nested_layout_adjacency_and_ancestor_counts() {
6471        let _runtime_session = crate::RuntimeSession::new();
6472        let _runtime_scope = _runtime_session.enter();
6473        let mut tree = ComponentTree::new();
6474        let root = tree.add_node(node_with_role("root", NodeRole::Composition, 1, 1));
6475        let outer = tree.add_node(node("outer", 2, 2));
6476        let inner = tree.add_node(node("inner", 3, 3));
6477        tree.add_node(node_with_role("wrapper", NodeRole::Composition, 4, 4));
6478        let leaf = tree.add_node(node("leaf", 5, 5));
6479        for _ in 0..5 {
6480            tree.pop_node();
6481        }
6482        for add_extra in [true, false] {
6483            let context = tree
6484                .begin_replace_subtree_by_instance_key(4)
6485                .unwrap_or_else(|_| panic!("wrapper exists"));
6486            let wrapper = tree.add_node(node_with_role("wrapper", NodeRole::Composition, 4, 4));
6487            assert_eq!(tree.attach_retained_subtree(5, 5), Some(leaf));
6488            tree.pop_node();
6489            let extra = add_extra.then(|| {
6490                let extra = tree.add_node(node("extra", 6, 6));
6491                tree.pop_node();
6492                extra
6493            });
6494            tree.pop_node();
6495            assert!(
6496                tree.finish_replace_subtree(context, &mut ReplayReplaceResult::default())
6497                    .is_ok()
6498            );
6499            tree.finish_replay_round();
6500            assert_eq!(direct_layout_children(root, &tree.tree).as_ref(), &[outer]);
6501            assert_eq!(direct_layout_children(outer, &tree.tree).as_ref(), &[inner]);
6502            let expected = extra.map_or_else(|| vec![leaf], |extra| vec![leaf, extra]);
6503            let actual = direct_layout_children(inner, &tree.tree);
6504            assert_eq!(actual.as_ref(), expected.as_slice());
6505            let wrapped = direct_layout_children(wrapper, &tree.tree);
6506            match (extra, actual, wrapped) {
6507                (Some(_), node::LayoutChildren::Shared(a), node::LayoutChildren::Shared(b)) => {
6508                    assert!(Arc::ptr_eq(&a, &b));
6509                }
6510                (None, node::LayoutChildren::One(a), node::LayoutChildren::One(b)) => {
6511                    assert_eq!((a, b), (leaf, leaf));
6512                }
6513                _ => panic!("layout adjacency must store a singleton inline and share wider lists"),
6514            }
6515            #[cfg(feature = "profiling")]
6516            for (id, base) in [(root, 5), (outer, 4), (inner, 3), (wrapper, 2)] {
6517                assert_eq!(
6518                    tree.get(id).unwrap().subtree_len,
6519                    base + usize::from(add_extra)
6520                );
6521            }
6522        }
6523    }
6524
6525    #[test]
6526    fn layout_adjacency_tracks_transparent_subtree_replacement() {
6527        let _runtime_session = crate::RuntimeSession::new();
6528        let _runtime_scope = _runtime_session.enter();
6529        let mut tree = ComponentTree::new();
6530
6531        let root = tree.add_node(node_with_role("root", NodeRole::Composition, 1, 1));
6532        let layout_a = tree.add_node(node("layout_a", 2, 2));
6533        let size = ComputedData {
6534            width: Px(31),
6535            height: Px(17),
6536        };
6537        tree.metadatas.entry_or_default(layout_a).computed_data = Some(size);
6538        tree.pop_node();
6539
6540        let composition = tree.add_node(node_with_role("wrapper", NodeRole::Composition, 3, 3));
6541        let layout_b = tree.add_node(node("layout_b", 4, 4));
6542        tree.pop_node();
6543        tree.pop_node();
6544        tree.pop_node();
6545
6546        assert_eq!(composition.children(tree.tree()).count(), 1);
6547        assert_eq!(
6548            direct_layout_children(root, tree.tree()).as_ref(),
6549            &[layout_a, layout_b]
6550        );
6551
6552        let context = tree
6553            .begin_replace_subtree_by_instance_key(3)
6554            .unwrap_or_else(|_| panic!("wrapper exists"));
6555        tree.add_node(node_with_role("replacement", NodeRole::Composition, 9, 9));
6556        assert_eq!(tree.attach_retained_subtree(4, 4), Some(layout_b));
6557        tree.pop_node();
6558        let layout_c = tree.add_node(node("layout_c", 10, 10));
6559        tree.pop_node();
6560        tree.pop_node();
6561        assert!(
6562            tree.finish_replace_subtree(context, &mut ReplayReplaceResult::default())
6563                .is_ok()
6564        );
6565
6566        let context = tree
6567            .begin_replace_subtree_by_instance_key(2)
6568            .unwrap_or_else(|_| panic!("first layout exists"));
6569        let layout_a = tree.add_node(node("replaced_layout_a", 2, 2));
6570        tree.pop_node();
6571        assert!(
6572            tree.finish_replace_subtree(context, &mut ReplayReplaceResult::default())
6573                .is_ok()
6574        );
6575        assert_eq!(
6576            tree.metadatas.get(&layout_a).unwrap().computed_data,
6577            Some(size)
6578        );
6579        tree.finish_replay_round();
6580        assert_eq!(
6581            direct_layout_children(root, tree.tree()).as_ref(),
6582            &[layout_a, layout_b, layout_c]
6583        );
6584
6585        let context = tree
6586            .begin_replace_subtree_by_instance_key(9)
6587            .unwrap_or_else(|_| panic!("replacement exists"));
6588        tree.add_node(node_with_role("reordered", NodeRole::Composition, 9, 9));
6589        for (key, id) in [(10, layout_c), (4, layout_b)] {
6590            assert_eq!(tree.attach_retained_subtree(key, key), Some(id));
6591            tree.pop_node();
6592        }
6593        tree.pop_node();
6594        assert!(
6595            tree.finish_replace_subtree(context, &mut ReplayReplaceResult::default())
6596                .is_ok()
6597        );
6598        tree.finish_replay_round();
6599        assert_eq!(
6600            direct_layout_children(root, tree.tree()).as_ref(),
6601            &[layout_a, layout_c, layout_b]
6602        );
6603    }
6604}
6605
6606#[cfg(all(test, feature = "testing"))]
6607#[path = "replay_journal_tests.rs"]
6608mod replay_journal_tests;