Skip to main content

tessera_ui/component_tree/
node.rs

1use std::{
2    collections::HashMap,
3    ops::{Add, AddAssign},
4    sync::Arc,
5};
6
7use indextree::NodeId;
8use rustc_hash::FxHashMap;
9use tracing::debug;
10use winit::window::CursorIcon;
11
12use crate::{
13    Px,
14    accessibility::{AccessibilityActionHandler, AccessibilityNode},
15    cursor::{CursorEventContent, PointerChange},
16    focus::{
17        FocusDirection, FocusRegistration, FocusRequester, FocusRevealRequest, FocusState,
18        FocusTraversalPolicy,
19    },
20    layout::{LayoutInput, LayoutPolicyDyn, LayoutResult, PlacementScope, RenderPolicyDyn},
21    modifier::{
22        LayoutModifierChild, LayoutModifierInput, LayoutModifierNode, Modifier,
23        OrderedModifierAction,
24    },
25    prop::CallbackWith,
26    px::{PxPosition, PxSize},
27    render_graph::RenderFragment,
28    runtime::{
29        RuntimePhase, push_current_component_instance_key,
30        push_current_node_with_instance_logic_id, push_phase,
31    },
32    time::Instant,
33};
34
35use super::{
36    LayoutContext, LayoutSnapshotEntry,
37    constraint::{Constraint, ParentConstraint},
38    nearest_replay_boundary_instance_key,
39};
40
41#[cfg(feature = "profiling")]
42use crate::profiler::{Phase as ProfilerPhase, ScopeGuard as ProfilerScopeGuard};
43
44/// A ComponentNode is a node in the component tree.
45/// It represents all information about a component.
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47pub(crate) enum NodeRole {
48    Composition,
49    Layout,
50}
51
52pub(crate) struct ComponentNode {
53    /// Component function's name, for debugging purposes.
54    pub(crate) fn_name: String,
55    /// Whether this tree node represents a composition boundary or an explicit
56    /// layout node.
57    pub(crate) role: NodeRole,
58    /// Stable logic identifier of this concrete component instance.
59    pub(crate) instance_logic_id: u64,
60    /// Stable instance identifier for this node in the current frame.
61    pub(crate) instance_key: u64,
62    /// Pointer input handlers for capture stage (root to leaf).
63    pub(crate) pointer_preview_handlers: Vec<Box<PointerInputHandlerFn>>,
64    /// Pointer input handlers for bubble stage (leaf to root).
65    pub(crate) pointer_handlers: Vec<Box<PointerInputHandlerFn>>,
66    /// Pointer input handlers for final stage (root to leaf).
67    pub(crate) pointer_final_handlers: Vec<Box<PointerInputHandlerFn>>,
68    /// Keyboard input handlers for preview stage (root to leaf).
69    pub(crate) keyboard_preview_handlers: Vec<Box<KeyboardInputHandlerFn>>,
70    /// Keyboard input handlers for bubble stage (leaf to root).
71    pub(crate) keyboard_handlers: Vec<Box<KeyboardInputHandlerFn>>,
72    /// IME input handlers for preview stage (root to leaf).
73    pub(crate) ime_preview_handlers: Vec<Box<ImeInputHandlerFn>>,
74    /// IME input handlers for bubble stage (leaf to root).
75    pub(crate) ime_handlers: Vec<Box<ImeInputHandlerFn>>,
76    /// Optional focus requester bound to this component node.
77    pub(crate) focus_requester_binding: Option<FocusRequester>,
78    /// Optional focus registration attached to this component node.
79    pub(crate) focus_registration: Option<FocusRegistration>,
80    /// Optional fallback target used when a focus scope restores focus.
81    pub(crate) focus_restorer_fallback: Option<FocusRequester>,
82    /// Optional traversal policy used when directional navigation starts inside
83    /// this focus group or scope.
84    pub(crate) focus_traversal_policy: Option<FocusTraversalPolicy>,
85    /// Optional callback invoked when the node's focus state changes.
86    pub(crate) focus_changed_handler: Option<FocusChangedHandler>,
87    /// Optional callback invoked when the node participates in a focus event.
88    pub(crate) focus_event_handler: Option<FocusEventHandler>,
89    /// Optional callback used to expand virtualized content for focus search.
90    pub(crate) focus_beyond_bounds_handler: Option<FocusBeyondBoundsHandler>,
91    /// Optional callback used to reveal the focused rectangle inside a
92    /// container.
93    pub(crate) focus_reveal_handler: Option<FocusRevealHandler>,
94    /// Node-local modifier chain attached during build.
95    pub(crate) modifier: Modifier,
96    /// Pure layout policy for measure and placement passes.
97    pub(crate) layout_policy: Box<dyn LayoutPolicyDyn>,
98    /// Render policy for recording draw and compute commands.
99    pub(crate) render_policy: Box<dyn RenderPolicyDyn>,
100    /// Optional replay metadata for subtree-level rerun.
101    pub(crate) replay: Option<crate::prop::ComponentReplayData>,
102    /// Whether props are equal to the previous frame snapshot.
103    pub(crate) props_unchanged_from_previous: bool,
104}
105
106/// Contains metadata of the component node.
107pub(crate) struct ComponentNodeMetaData {
108    /// The computed data (size) of the node.
109    /// None if the node is not computed yet.
110    pub computed_data: Option<ComputedData>,
111    /// Whether the layout cache was hit for this node in the current frame.
112    pub layout_cache_hit: bool,
113    /// Placement order among siblings within the parent layout result.
114    pub placement_order: Option<u64>,
115    /// The node's start position, relative to its parent.
116    /// None if the node is not placed yet.
117    pub rel_position: Option<PxPosition>,
118    /// The node's start position, relative to the root window.
119    /// Before placement modifiers are applied.
120    /// This will be computed during drawing command's generation.
121    /// None if the node is not drawn yet.
122    pub base_abs_position: Option<PxPosition>,
123    /// The node's start position, relative to the root window.
124    /// After placement modifiers are applied.
125    /// This will be computed during drawing command's generation.
126    /// None if the node is not drawn yet.
127    pub abs_position: Option<PxPosition>,
128    /// The effective clipping rectangle for this node, considering all its
129    /// ancestors. This is calculated once per frame before event handling.
130    pub event_clip_rect: Option<crate::PxRect>,
131    /// Render fragment associated with this node.
132    ///
133    /// Commands are collected during the record phase and merged into the frame
134    /// graph during rendering.
135    pub(crate) fragment: RenderFragment,
136    /// Whether this node clips its children.
137    pub clips_children: bool,
138    /// Opacity multiplier applied to this node and its descendants.
139    pub opacity: f32,
140    /// Accessibility information for this node.
141    pub accessibility: Option<AccessibilityNode>,
142    /// Handler for accessibility actions on this node.
143    pub accessibility_action_handler: Option<AccessibilityActionHandler>,
144}
145
146impl ComponentNodeMetaData {
147    /// Creates a new `ComponentNodeMetaData` with default values.
148    pub fn none() -> Self {
149        Self {
150            computed_data: None,
151            layout_cache_hit: false,
152            placement_order: None,
153            rel_position: None,
154            base_abs_position: None,
155            abs_position: None,
156            event_clip_rect: None,
157            fragment: RenderFragment::default(),
158            clips_children: false,
159            opacity: 1.0,
160            accessibility: None,
161            accessibility_action_handler: None,
162        }
163    }
164
165    /// Returns a mutable render fragment for this node.
166    pub fn fragment_mut(&mut self) -> &mut RenderFragment {
167        &mut self.fragment
168    }
169
170    /// Takes the current fragment and replaces it with an empty one.
171    pub(crate) fn take_fragment(&mut self) -> RenderFragment {
172        std::mem::take(&mut self.fragment)
173    }
174}
175
176impl Default for ComponentNodeMetaData {
177    fn default() -> Self {
178        Self::none()
179    }
180}
181
182pub(crate) fn direct_layout_children(node_id: NodeId, tree: &ComponentNodeTree) -> Vec<NodeId> {
183    fn collect(node_id: NodeId, tree: &ComponentNodeTree, output: &mut Vec<NodeId>) {
184        let Some(node_ref) = tree.get(node_id) else {
185            return;
186        };
187        if node_ref.get().role == NodeRole::Layout {
188            output.push(node_id);
189            return;
190        }
191
192        for child_id in node_id.children(tree) {
193            collect(child_id, tree, output);
194        }
195    }
196
197    let mut children = Vec::new();
198    for child_id in node_id.children(tree) {
199        collect(child_id, tree, &mut children);
200    }
201    children
202}
203
204fn reset_frame_metadata(node_id: NodeId, component_node_metadatas: &mut ComponentNodeMetaDatas) {
205    let metadata = component_node_metadatas.entry_or_default(node_id);
206    metadata.computed_data = None;
207    metadata.layout_cache_hit = false;
208    metadata.placement_order = None;
209    metadata.rel_position = None;
210    metadata.base_abs_position = None;
211    metadata.abs_position = None;
212    metadata.event_clip_rect = None;
213    metadata.fragment = RenderFragment::default();
214    metadata.clips_children = false;
215    metadata.opacity = 1.0;
216}
217
218/// A tree of component nodes, using `indextree::Arena` for storage.
219pub(crate) type ComponentNodeTree = indextree::Arena<ComponentNode>;
220
221/// Stores component metadata for a single UI tree.
222#[derive(Default)]
223pub(crate) struct ComponentNodeMetaDatas {
224    entries: FxHashMap<NodeId, ComponentNodeMetaData>,
225}
226
227impl ComponentNodeMetaDatas {
228    pub(crate) fn new() -> Self {
229        Self::default()
230    }
231
232    pub(crate) fn clear(&mut self) {
233        self.entries.clear();
234    }
235
236    pub(crate) fn insert(
237        &mut self,
238        node_id: NodeId,
239        metadata: ComponentNodeMetaData,
240    ) -> Option<ComponentNodeMetaData> {
241        self.entries.insert(node_id, metadata)
242    }
243
244    pub(crate) fn remove(&mut self, node_id: &NodeId) -> Option<ComponentNodeMetaData> {
245        self.entries.remove(node_id)
246    }
247
248    pub(crate) fn get(&self, node_id: &NodeId) -> Option<&ComponentNodeMetaData> {
249        self.entries.get(node_id)
250    }
251
252    pub(crate) fn get_mut(&mut self, node_id: &NodeId) -> Option<&mut ComponentNodeMetaData> {
253        self.entries.get_mut(node_id)
254    }
255
256    pub(crate) fn entry_or_default(&mut self, node_id: NodeId) -> &mut ComponentNodeMetaData {
257        self.entries.entry(node_id).or_default()
258    }
259
260    #[cfg(feature = "testing")]
261    pub(crate) fn with_entries<R>(
262        &self,
263        f: impl FnOnce(&FxHashMap<NodeId, ComponentNodeMetaData>) -> R,
264    ) -> R {
265        f(&self.entries)
266    }
267}
268
269/// Represents errors that can occur during node measurement.
270#[derive(Debug, Clone, PartialEq)]
271pub enum MeasurementError {
272    /// Indicates that the specified node was not found in the component tree.
273    NodeNotFoundInTree,
274    /// Indicates that metadata for the specified node was not found (currently
275    /// not a primary error source in measure_node).
276    NodeNotFoundInMeta,
277    /// Indicates that the layout policy for a node failed. Contains a string
278    /// detailing the failure.
279    MeasureFnFailed(String),
280    /// Indicates that the measurement of a child node failed during a
281    /// parent's layout calculation. Contains the `NodeId` of the child
282    /// that failed.
283    ChildMeasurementFailed(NodeId),
284}
285
286/// Pointer input dispatch pass.
287#[derive(Debug, Clone, Copy, PartialEq, Eq)]
288pub enum PointerEventPass {
289    /// Dispatch from root to leaf before the main pointer pass.
290    Initial,
291    /// Main pointer dispatch from leaf to root.
292    Main,
293    /// Final pointer dispatch from root to leaf.
294    Final,
295}
296
297/// Pointer-specific input handler.
298pub type PointerInputHandlerFn = dyn Fn(PointerInput) + Send + Sync;
299/// Keyboard-specific input handler.
300pub type KeyboardInputHandlerFn = dyn Fn(KeyboardInput) + Send + Sync;
301/// IME-specific input handler.
302pub type ImeInputHandlerFn = dyn Fn(ImeInput) + Send + Sync;
303/// Focus-changed callback attached to a component node.
304pub type FocusChangedHandler = CallbackWith<FocusState>;
305/// Focus-event callback attached to a component node.
306pub type FocusEventHandler = CallbackWith<FocusState>;
307/// Beyond-bounds focus callback attached to a virtualized container node.
308pub type FocusBeyondBoundsHandler = CallbackWith<FocusDirection, bool>;
309/// Reveal callback attached to a scrollable container node.
310pub type FocusRevealHandler = CallbackWith<FocusRevealRequest, bool>;
311
312/// Input for pointer handlers.
313///
314/// This type carries pointer event payload, local geometry, and event
315/// consumption helpers. Side effects that are not pointer-specific should use
316/// dedicated APIs such as semantics modifiers, hover cursor modifiers, window
317/// action helpers, or the IME session bridge instead of a generic request bag.
318pub struct PointerInput<'a> {
319    /// Current pointer dispatch pass.
320    pub pass: PointerEventPass,
321    /// The size of the component node, computed during the measure stage.
322    pub computed_data: ComputedData,
323    /// The position of the cursor, if available.
324    /// Relative to the root position of the component.
325    pub cursor_position_rel: Option<PxPosition>,
326    /// Absolute cursor position in window coordinates.
327    pub(crate) cursor_position_abs: &'a mut Option<PxPosition>,
328    /// Pointer changes from the event loop, if any.
329    pub pointer_changes: &'a mut Vec<PointerChange>,
330    /// The current state of the keyboard modifiers at the time of the event.
331    pub key_modifiers: winit::keyboard::ModifiersState,
332    pub(crate) ime_request: &'a mut Option<ImeRequest>,
333    pub(crate) request_window_drag: &'a mut bool,
334}
335
336impl PointerInput<'_> {
337    /// Marks all current pointer changes as consumed.
338    pub fn consume_pointer_changes(&mut self) {
339        for change in self.pointer_changes.iter_mut() {
340            change.consume();
341        }
342    }
343
344    /// Returns whether any current pointer change is an unconsumed release.
345    pub fn has_unconsumed_release(&self) -> bool {
346        self.pointer_changes.iter().any(|change| {
347            !change.is_consumed() && matches!(change.content, CursorEventContent::Released(_))
348        })
349    }
350
351    /// Returns the absolute cursor position in window coordinates.
352    pub fn cursor_position_abs(&self) -> Option<PxPosition> {
353        *self.cursor_position_abs
354    }
355
356    /// Blocks pointer input to other components.
357    pub fn block_cursor(&mut self) {
358        self.cursor_position_abs.take();
359        self.consume_pointer_changes();
360    }
361
362    /// Blocks pointer changes to other components.
363    pub fn block_all(&mut self) {
364        self.block_cursor();
365    }
366
367    /// Begins a system drag move for the current window.
368    pub fn drag_window(&mut self) {
369        *self.request_window_drag = true;
370    }
371
372    /// Returns the IME session bridge for the current frame.
373    pub fn ime_session(&mut self) -> ImeSession<'_> {
374        ImeSession {
375            request: self.ime_request,
376        }
377    }
378}
379
380/// Input for keyboard handlers.
381///
382/// This type carries keyboard event payload and event consumption helpers.
383/// IME updates, semantics, and other side effects are exposed through narrower
384/// dedicated APIs rather than a general-purpose request sink.
385pub struct KeyboardInput<'a> {
386    /// The size of the component node, computed during the measure stage.
387    pub computed_data: ComputedData,
388    /// Keyboard events from the event loop, if any.
389    pub keyboard_events: &'a mut Vec<winit::event::KeyEvent>,
390    /// The current state of the keyboard modifiers at the time of the event.
391    pub key_modifiers: winit::keyboard::ModifiersState,
392    pub(crate) ime_request: &'a mut Option<ImeRequest>,
393}
394
395impl KeyboardInput<'_> {
396    /// Blocks keyboard events to other components.
397    pub fn block_keyboard(&mut self) {
398        self.keyboard_events.clear();
399    }
400
401    /// Returns the IME session bridge for the current frame.
402    pub fn ime_session(&mut self) -> ImeSession<'_> {
403        ImeSession {
404            request: self.ime_request,
405        }
406    }
407}
408
409/// Input for IME handlers.
410///
411/// This type carries IME event payload and event consumption helpers. IME
412/// snapshot publication is performed through [`ImeSession`].
413pub struct ImeInput<'a> {
414    /// The size of the component node, computed during the measure stage.
415    pub computed_data: ComputedData,
416    /// IME events from the event loop, if any.
417    pub ime_events: &'a mut Vec<winit::event::Ime>,
418    pub(crate) ime_request: &'a mut Option<ImeRequest>,
419}
420
421impl ImeInput<'_> {
422    /// Blocks IME events to other components.
423    pub fn block_ime(&mut self) {
424        self.ime_events.clear();
425    }
426
427    /// Returns the IME session bridge for the current frame.
428    pub fn ime_session(&mut self) -> ImeSession<'_> {
429        ImeSession {
430            request: self.ime_request,
431        }
432    }
433}
434
435/// A collection of requests that components can make to the windowing system
436/// for the current frame. This struct's lifecycle is confined to a single
437/// `compute` pass.
438#[derive(Default, Debug)]
439pub(crate) struct WindowRequests {
440    /// The cursor icon requested by a component. If multiple components request
441    /// a cursor, the last one to make a request in a frame "wins", since
442    /// it's executed later.
443    pub cursor_icon: CursorIcon,
444    /// An Input Method Editor (IME) request.
445    /// If multiple components request IME, the one from the "newer" component
446    /// (which is processed later in the state handling pass) will overwrite
447    /// previous requests.
448    pub ime_request: Option<ImeRequest>,
449    /// Whether a node requested a native window drag for the current frame.
450    pub request_window_drag: bool,
451}
452
453/// Frame-local IME bridge used by input handlers to publish text input state.
454///
455/// Use this bridge when an input handler needs to expose the current text-input
456/// snapshot to the renderer-facing IME integration without mutating renderer
457/// aggregation state directly.
458pub struct ImeSession<'a> {
459    request: &'a mut Option<ImeRequest>,
460}
461
462impl ImeSession<'_> {
463    /// Publishes the IME snapshot for the current frame.
464    pub fn update(&mut self, request: ImeRequest) {
465        *self.request = Some(request);
466    }
467
468    /// Clears the IME snapshot for the current frame.
469    pub fn clear(&mut self) {
470        *self.request = None;
471    }
472}
473
474/// A request to the windowing system to open an Input Method Editor (IME).
475/// This is typically used for text input components.
476#[derive(Clone, Debug, PartialEq, Eq)]
477pub struct ImeRequest {
478    /// The size of the area where the IME is requested.
479    pub size: PxSize,
480    /// The position of the IME anchor relative to the requesting node.
481    pub local_position: PxPosition,
482    /// The current ordered selection range in the backing text buffer.
483    pub selection_range: Option<std::ops::Range<usize>>,
484    /// The current ordered composition range in the backing text buffer.
485    pub composition_range: Option<std::ops::Range<usize>>,
486    /// The absolute position where the IME should be placed.
487    /// This is set internally by the component tree during the compute pass.
488    pub(crate) position: Option<PxPosition>, // should be setted in tessera node tree compute
489}
490
491impl ImeRequest {
492    /// Creates a new IME request with the target input area size.
493    ///
494    /// The absolute position is injected during the compute pass.
495    pub fn new(size: PxSize) -> Self {
496        Self {
497            size,
498            local_position: PxPosition::ZERO,
499            selection_range: None,
500            composition_range: None,
501            position: None, // Position will be set during the compute phase
502        }
503    }
504
505    /// Sets the position of the IME anchor relative to the requesting node.
506    pub fn with_local_position(mut self, local_position: PxPosition) -> Self {
507        self.local_position = local_position;
508        self
509    }
510
511    /// Sets the current ordered text selection range.
512    pub fn with_selection_range(mut self, selection_range: Option<std::ops::Range<usize>>) -> Self {
513        self.selection_range = selection_range;
514        self
515    }
516
517    /// Sets the current ordered text composition range.
518    pub fn with_composition_range(
519        mut self,
520        composition_range: Option<std::ops::Range<usize>>,
521    ) -> Self {
522        self.composition_range = composition_range;
523        self
524    }
525}
526
527fn apply_layout_placements(
528    placements: &[(u64, PxPosition)],
529    tree: &ComponentNodeTree,
530    children: &[NodeId],
531    component_node_metadatas: &mut ComponentNodeMetaDatas,
532) {
533    if placements.is_empty() || children.is_empty() {
534        return;
535    }
536    let mut child_map = HashMap::new();
537    for child_id in children {
538        if let Some(child) = tree.get(*child_id) {
539            child_map.insert(child.get().instance_key, *child_id);
540        }
541    }
542    for (placement_order, (instance_key, position)) in placements.iter().enumerate() {
543        if let Some(child_id) = child_map.get(instance_key) {
544            place_node(
545                *child_id,
546                *position,
547                placement_order as u64,
548                component_node_metadatas,
549            );
550        }
551    }
552}
553
554fn restore_cached_subtree_metadata(
555    node_id: NodeId,
556    rel_position: Option<PxPosition>,
557    tree: &ComponentNodeTree,
558    component_node_metadatas: &mut ComponentNodeMetaDatas,
559    layout_ctx: &LayoutContext<'_>,
560) -> bool {
561    let Some(node) = tree.get(node_id) else {
562        return false;
563    };
564    let instance_key = node.get().instance_key;
565    let Some(entry) = layout_ctx.snapshot(instance_key) else {
566        return false;
567    };
568
569    let size = entry.layout_result.size;
570    let placements = entry.layout_result.placements.clone();
571
572    {
573        let metadata = component_node_metadatas.entry_or_default(node_id);
574        metadata.computed_data = Some(size);
575        metadata.layout_cache_hit = true;
576        if let Some(position) = rel_position {
577            metadata.rel_position = Some(position);
578        }
579    }
580
581    let mut child_positions = HashMap::new();
582    let mut child_orders = HashMap::new();
583    for (placement_order, (child_key, child_pos)) in placements.into_iter().enumerate() {
584        child_positions.insert(child_key, child_pos);
585        child_orders.insert(child_key, placement_order as u64);
586    }
587
588    for child_id in node_id.children(tree) {
589        let child_layout = tree.get(child_id).map(|child| {
590            let instance_key = child.get().instance_key;
591            (
592                child_positions.get(&instance_key).copied(),
593                child_orders.get(&instance_key).copied(),
594            )
595        });
596        let (child_rel_position, child_placement_order) = child_layout.unwrap_or((None, None));
597        if let Some(placement_order) = child_placement_order {
598            component_node_metadatas
599                .entry_or_default(child_id)
600                .placement_order = Some(placement_order);
601        }
602        if !restore_cached_subtree_metadata(
603            child_id,
604            child_rel_position,
605            tree,
606            component_node_metadatas,
607            layout_ctx,
608        ) {
609            return false;
610        }
611    }
612
613    true
614}
615
616#[derive(Clone)]
617struct MeasuredNodeLayout {
618    size: ComputedData,
619    placements: Vec<(u64, PxPosition)>,
620    measured_children: HashMap<NodeId, crate::layout::ChildMeasure>,
621}
622
623struct MeasureLayoutContext<'a, 'ctx> {
624    tree: &'a ComponentNodeTree,
625    children: &'a [NodeId],
626    component_node_metadatas: *mut ComponentNodeMetaDatas,
627    layout_ctx: Option<&'a LayoutContext<'ctx>>,
628}
629
630fn measure_base_layout(
631    layout_policy: &dyn LayoutPolicyDyn,
632    layout_ctx: &MeasureLayoutContext<'_, '_>,
633    constraint: &Constraint,
634) -> Result<MeasuredNodeLayout, MeasurementError> {
635    let input = LayoutInput::new(
636        layout_ctx.tree,
637        ParentConstraint::new(constraint),
638        layout_ctx.children,
639        layout_ctx.component_node_metadatas,
640        layout_ctx.layout_ctx,
641    );
642    let scope = input.measure_scope();
643    let layout_result = layout_policy.measure_dyn(&scope)?;
644    Ok(MeasuredNodeLayout {
645        size: layout_result.size,
646        placements: layout_result.placements,
647        measured_children: input.take_measured_children(),
648    })
649}
650
651fn measure_with_layout_modifiers(
652    modifiers: &[Arc<dyn LayoutModifierNode>],
653    layout_policy: &dyn LayoutPolicyDyn,
654    layout_ctx: &MeasureLayoutContext<'_, '_>,
655    constraint: &Constraint,
656) -> Result<MeasuredNodeLayout, MeasurementError> {
657    if modifiers.is_empty() {
658        return measure_base_layout(layout_policy, layout_ctx, constraint);
659    }
660
661    struct ModifierChildRunner<'a, 'b> {
662        next: &'b mut dyn FnMut(&Constraint) -> Result<MeasuredNodeLayout, MeasurementError>,
663        last: Option<MeasuredNodeLayout>,
664        placements: Vec<(u64, PxPosition)>,
665        _marker: std::marker::PhantomData<&'a ()>,
666    }
667
668    impl LayoutModifierChild for ModifierChildRunner<'_, '_> {
669        fn measure(&mut self, constraint: &Constraint) -> Result<ComputedData, MeasurementError> {
670            let measured = (self.next)(constraint)?;
671            let size = measured.size;
672            self.last = Some(measured);
673            Ok(size)
674        }
675
676        fn place(&mut self, position: PxPosition) {
677            let measured = self
678                .last
679                .as_ref()
680                .expect("layout modifier child must be measured before placement");
681            for (instance_key, child_position) in &measured.placements {
682                self.placements
683                    .push((*instance_key, position + *child_position));
684            }
685        }
686    }
687
688    let head = &modifiers[0];
689    let tail = &modifiers[1..];
690    let mut next = |next_constraint: &Constraint| {
691        measure_with_layout_modifiers(tail, layout_policy, layout_ctx, next_constraint)
692    };
693    let input = LayoutInput::new(
694        layout_ctx.tree,
695        ParentConstraint::new(constraint),
696        layout_ctx.children,
697        layout_ctx.component_node_metadatas,
698        layout_ctx.layout_ctx,
699    );
700    let modifier_input = LayoutModifierInput {
701        layout_input: &input,
702    };
703    let mut child = ModifierChildRunner {
704        next: &mut next,
705        last: None,
706        placements: Vec::new(),
707        _marker: std::marker::PhantomData,
708    };
709    let size = head.measure(&modifier_input, &mut child)?.size;
710    if let Some(measured) = child.last.as_ref() {
711        input.extend_measured_children(measured.measured_children.clone());
712    }
713    Ok(MeasuredNodeLayout {
714        size,
715        placements: child.placements,
716        measured_children: input.take_measured_children(),
717    })
718}
719
720fn relayout_base_layout(
721    layout_policy: &dyn LayoutPolicyDyn,
722    tree: &ComponentNodeTree,
723    children: &[NodeId],
724    cached_child_sizes: &[ComputedData],
725    constraint: &Constraint,
726    cached_size: ComputedData,
727) -> Option<Vec<(u64, PxPosition)>> {
728    if cached_child_sizes.len() != children.len() {
729        return None;
730    }
731
732    let child_sizes: HashMap<NodeId, ComputedData> = children
733        .iter()
734        .copied()
735        .zip(cached_child_sizes.iter().copied())
736        .collect();
737    let scope = PlacementScope::new(
738        tree,
739        ParentConstraint::new(constraint),
740        children,
741        &child_sizes,
742        cached_size,
743    );
744    layout_policy.place_children_dyn(&scope)
745}
746
747/// Measures a single node recursively, returning its size or an error.
748pub(crate) fn measure_node(
749    node_id: NodeId,
750    parent_constraint: &Constraint,
751    tree: &ComponentNodeTree,
752    component_node_metadatas: &mut ComponentNodeMetaDatas,
753    layout_ctx: Option<&LayoutContext<'_>>,
754) -> Result<ComputedData, MeasurementError> {
755    let node_data_ref = tree
756        .get(node_id)
757        .ok_or(MeasurementError::NodeNotFoundInTree)?;
758    let node_data = node_data_ref.get();
759    #[cfg(feature = "profiling")]
760    let mut profiler_guard = Some(ProfilerScopeGuard::new(
761        ProfilerPhase::Measure,
762        Some(node_id),
763        node_data_ref.parent(),
764        Some(node_data.fn_name.as_str()),
765    ));
766
767    let children = direct_layout_children(node_id, tree);
768    let timer = Instant::now();
769
770    debug!(
771        "Measuring node {} with {} children, parent constraint: {:?}",
772        node_data.fn_name,
773        children.len(),
774        parent_constraint
775    );
776
777    // Ensure thread-local current node context for nested control-flow
778    // instrumentation.
779    let _node_ctx_guard = push_current_node_with_instance_logic_id(
780        node_id,
781        node_data.instance_logic_id,
782        node_data.fn_name.as_str(),
783    );
784    let replay_boundary_instance_key = nearest_replay_boundary_instance_key(node_id, tree);
785    let _instance_ctx_guard = push_current_component_instance_key(replay_boundary_instance_key);
786    let _phase_guard = push_phase(RuntimePhase::Measure);
787
788    let layout_policy = &node_data.layout_policy;
789    let layout_modifiers: Vec<_> = node_data
790        .modifier
791        .ordered_actions()
792        .into_iter()
793        .filter_map(|action| match action {
794            OrderedModifierAction::Layout(node) => Some(node.node()),
795            _ => None,
796        })
797        .collect();
798    if let Some(layout_ctx) = layout_ctx {
799        layout_ctx.inc_measure_node_calls();
800        if let Some(entry) = layout_ctx.snapshot(node_data.instance_key) {
801            let same_constraint = entry.constraint_key == *parent_constraint;
802            let node_self_measure_dirty = layout_ctx
803                .measure_self_nodes
804                .contains(&node_data.instance_key);
805            let node_self_placement_dirty = layout_ctx
806                .placement_self_nodes
807                .contains(&node_data.instance_key);
808            let node_effective_dirty = layout_ctx
809                .dirty_effective_nodes
810                .contains(&node_data.instance_key);
811            let has_all_child_constraints = entry.child_constraints.len() == children.len();
812            let has_all_child_sizes = entry.child_sizes.len() == children.len();
813            let can_try_reuse = same_constraint
814                && !node_self_measure_dirty
815                && has_all_child_constraints
816                && (!node_effective_dirty || has_all_child_sizes);
817            if can_try_reuse {
818                let cached_result = entry.layout_result.clone();
819                let cached_child_constraints = entry.child_constraints.clone();
820                let cached_child_sizes = entry.child_sizes.clone();
821
822                if !node_effective_dirty
823                    && restore_cached_subtree_metadata(
824                        node_id,
825                        None,
826                        tree,
827                        component_node_metadatas,
828                        layout_ctx,
829                    )
830                {
831                    apply_layout_placements(
832                        &cached_result.placements,
833                        tree,
834                        &children,
835                        component_node_metadatas,
836                    );
837                    layout_ctx.inc_cache_hit_direct();
838                    return Ok(cached_result.size);
839                }
840
841                let dirty_children: Vec<(usize, NodeId, Constraint)> = children
842                    .iter()
843                    .enumerate()
844                    .filter_map(|(index, child_id)| {
845                        tree.get(*child_id).and_then(|child| {
846                            if layout_ctx
847                                .dirty_effective_nodes
848                                .contains(&child.get().instance_key)
849                            {
850                                Some((index, *child_id, cached_child_constraints[index]))
851                            } else {
852                                None
853                            }
854                        })
855                    })
856                    .collect();
857
858                let mut child_size_changed = false;
859                for (index, child_id, _constraint) in &dirty_children {
860                    let size = measure_node(
861                        *child_id,
862                        &cached_child_constraints[*index],
863                        tree,
864                        component_node_metadatas,
865                        Some(layout_ctx),
866                    )?;
867                    if size != cached_child_sizes[*index] {
868                        child_size_changed = true;
869                        break;
870                    }
871                }
872                if child_size_changed {
873                    layout_ctx.inc_cache_miss_child_size();
874                } else {
875                    let mut restored = true;
876                    for child_id in &children {
877                        let Some(child) = tree.get(*child_id) else {
878                            restored = false;
879                            break;
880                        };
881                        if layout_ctx
882                            .dirty_effective_nodes
883                            .contains(&child.get().instance_key)
884                        {
885                            continue;
886                        }
887                        if !restore_cached_subtree_metadata(
888                            *child_id,
889                            None,
890                            tree,
891                            component_node_metadatas,
892                            layout_ctx,
893                        ) {
894                            restored = false;
895                            break;
896                        }
897                    }
898                    if restored {
899                        reset_frame_metadata(node_id, component_node_metadatas);
900                        let placements = if node_self_placement_dirty {
901                            match relayout_base_layout(
902                                layout_policy.as_ref(),
903                                tree,
904                                &children,
905                                &cached_child_sizes,
906                                parent_constraint,
907                                cached_result.size,
908                            ) {
909                                Some(placements) => placements,
910                                None => {
911                                    layout_ctx.inc_cache_miss_dirty_self();
912                                    // This node can reuse measurement but not placements; fall back
913                                    // to a full measure+place pass below.
914                                    Vec::new()
915                                }
916                            }
917                        } else {
918                            cached_result.placements.clone()
919                        };
920                        if node_self_placement_dirty && placements.is_empty() {
921                            // Fall through to the full measure path below.
922                        } else {
923                            apply_layout_placements(
924                                &placements,
925                                tree,
926                                &children,
927                                component_node_metadatas,
928                            );
929                            if let Some(metadata) = component_node_metadatas.get_mut(&node_id) {
930                                metadata.computed_data = Some(cached_result.size);
931                                metadata.layout_cache_hit = true;
932                            }
933                            if node_self_placement_dirty {
934                                layout_ctx.insert_snapshot(
935                                    node_data.instance_key,
936                                    LayoutSnapshotEntry {
937                                        constraint_key: *parent_constraint,
938                                        layout_result: LayoutResult {
939                                            size: cached_result.size,
940                                            placements,
941                                        },
942                                        child_constraints: cached_child_constraints,
943                                        child_sizes: cached_child_sizes,
944                                    },
945                                );
946                            }
947                            layout_ctx.inc_cache_hit_boundary();
948                            return Ok(cached_result.size);
949                        }
950                    }
951                }
952            }
953            if !same_constraint {
954                layout_ctx.inc_cache_miss_constraint();
955            } else if node_self_measure_dirty || node_self_placement_dirty {
956                layout_ctx.inc_cache_miss_dirty_self();
957            } else if node_effective_dirty {
958                layout_ctx.inc_cache_miss_child_size();
959            } else {
960                layout_ctx.inc_cache_miss_no_entry();
961            }
962        } else {
963            layout_ctx.inc_cache_miss_no_entry();
964        }
965    }
966
967    reset_frame_metadata(node_id, component_node_metadatas);
968    let measure_layout_ctx = MeasureLayoutContext {
969        tree,
970        children: &children,
971        component_node_metadatas,
972        layout_ctx,
973    };
974    let measured = measure_with_layout_modifiers(
975        &layout_modifiers,
976        layout_policy.as_ref(),
977        &measure_layout_ctx,
978        parent_constraint,
979    )?;
980    let size = measured.size;
981    let measured_children = measured.measured_children;
982    let placements = measured.placements;
983    apply_layout_placements(&placements, tree, &children, component_node_metadatas);
984
985    component_node_metadatas
986        .entry_or_default(node_id)
987        .computed_data = Some(size);
988
989    #[cfg(feature = "profiling")]
990    if let Some(guard) = &mut profiler_guard {
991        guard.set_computed_size(size.width.0, size.height.0);
992    }
993
994    if let Some(layout_ctx) = layout_ctx {
995        let mut cacheable = true;
996        let mut child_constraints = Vec::with_capacity(children.len());
997        let mut child_sizes = Vec::with_capacity(children.len());
998        for child_id in &children {
999            let Some(measurement) = measured_children.get(child_id) else {
1000                cacheable = false;
1001                break;
1002            };
1003            if !measurement.consistent {
1004                cacheable = false;
1005                break;
1006            }
1007            child_constraints.push(measurement.constraint);
1008            child_sizes.push(measurement.size);
1009        }
1010        if cacheable {
1011            let layout_result = LayoutResult { size, placements };
1012            layout_ctx.insert_snapshot(
1013                node_data.instance_key,
1014                LayoutSnapshotEntry {
1015                    constraint_key: *parent_constraint,
1016                    layout_result,
1017                    child_constraints,
1018                    child_sizes,
1019                },
1020            );
1021            layout_ctx.inc_cache_store_count();
1022        } else {
1023            layout_ctx.remove_snapshot(node_data.instance_key);
1024            layout_ctx.inc_cache_drop_non_cacheable_count();
1025        }
1026    }
1027
1028    debug!(
1029        "Measured node {} in {:?} with size {:?}",
1030        node_data.fn_name,
1031        timer.elapsed(),
1032        size
1033    );
1034
1035    #[cfg(feature = "profiling")]
1036    if let Some(guard) = &mut profiler_guard {
1037        guard.set_computed_size(size.width.0, size.height.0);
1038    }
1039
1040    Ok(size)
1041}
1042
1043/// Places a node at the specified relative position within its parent.
1044pub(crate) fn place_node(
1045    node: indextree::NodeId,
1046    rel_position: PxPosition,
1047    placement_order: u64,
1048    component_node_metadatas: &mut ComponentNodeMetaDatas,
1049) {
1050    let metadata = component_node_metadatas.entry_or_default(node);
1051    metadata.rel_position = Some(rel_position);
1052    metadata.placement_order = Some(placement_order);
1053}
1054
1055/// Layout information computed at the measure stage, representing the size of a
1056/// node.
1057#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1058pub struct ComputedData {
1059    /// The resolved width of the node in physical pixels.
1060    pub width: Px,
1061    /// The resolved height of the node in physical pixels.
1062    pub height: Px,
1063}
1064
1065impl Add for ComputedData {
1066    type Output = Self;
1067    fn add(self, rhs: Self) -> Self::Output {
1068        Self {
1069            width: self.width + rhs.width,
1070            height: self.height + rhs.height,
1071        }
1072    }
1073}
1074
1075impl AddAssign for ComputedData {
1076    fn add_assign(&mut self, rhs: Self) {
1077        *self = *self + rhs;
1078    }
1079}
1080
1081impl ComputedData {
1082    /// Zero-sized layout data.
1083    pub const ZERO: Self = Self {
1084        width: Px(0),
1085        height: Px(0),
1086    };
1087
1088    /// Calculates the minimum size guaranteed by a constraint interval.
1089    pub fn min_from_constraint(constraint: &Constraint) -> Self {
1090        let width = constraint.width.min;
1091        let height = constraint.height.min;
1092        Self { width, height }
1093    }
1094
1095    /// Returns the component-wise minimum of two computed sizes.
1096    pub fn min(self, rhs: Self) -> Self {
1097        Self {
1098            width: self.width.min(rhs.width),
1099            height: self.height.min(rhs.height),
1100        }
1101    }
1102
1103    /// Returns the component-wise maximum of two computed sizes.
1104    pub fn max(self, rhs: Self) -> Self {
1105        Self {
1106            width: self.width.max(rhs.width),
1107            height: self.height.max(rhs.height),
1108        }
1109    }
1110}