Skip to main content

cranpose_ui/layout/
mod.rs

1pub mod core;
2pub mod policies;
3
4use std::{
5    cell::{Cell, RefCell},
6    fmt,
7    mem::size_of,
8    rc::Rc,
9    sync::OnceLock,
10};
11
12use cranpose_core::{
13    Applier, ApplierHost, Composer, ConcreteApplierHost, MemoryApplier, Node, NodeError, NodeId,
14    Phase, RuntimeHandle, SlotTable, SlotsHost, SnapshotStateObserver,
15};
16use cranpose_foundation::{
17    CanvasSemanticsNode, CollectionInfo, InvalidationKind, LiveRegionMode, ModifierNodeContext,
18    NodeCapabilities, ProgressBarRangeInfo, ScrollAxisRange, SemanticsConfiguration,
19    SemanticsCustomAction, SemanticsDismiss, SemanticsExpand, SemanticsLongClick,
20    SemanticsScrollBy, SemanticsScrollToIndex, SemanticsSetProgress, SemanticsSetText,
21    SemanticsWidgetRole, text::TextRange,
22};
23use cranpose_ui_layout::{Constraints, MeasurePolicy, Placement};
24use web_time::Instant;
25
26#[cfg(test)]
27use self::core::{HorizontalAlignment, VerticalAlignment};
28use self::core::{Measurable, Placeable};
29use crate::{
30    modifier::{
31        DimensionConstraint, EdgeInsets, Modifier, ModifierNodeSlices,
32        ModifierNodeSlicesDebugStats, Point, Rect as GeometryRect, ResolvedModifiers, Size,
33        collect_semantics_from_modifier,
34    },
35    subcompose_layout::{CachedBatchMeasureInputs, SubcomposeLayoutNode},
36    widgets::nodes::{IntrinsicKind, LayoutNode, LayoutNodeCacheHandles, LayoutState},
37};
38
39#[derive(Default)]
40pub(crate) struct LayoutNodeContext {
41    invalidations: Vec<InvalidationKind>,
42    update_requested: bool,
43    active_capabilities: Vec<NodeCapabilities>,
44}
45
46impl LayoutNodeContext {
47    pub(crate) fn new() -> Self {
48        Self::default()
49    }
50
51    pub(crate) fn take_invalidations(&mut self) -> Vec<InvalidationKind> {
52        std::mem::take(&mut self.invalidations)
53    }
54}
55
56impl ModifierNodeContext for LayoutNodeContext {
57    fn invalidate(&mut self, kind: InvalidationKind) {
58        if !self.invalidations.contains(&kind) {
59            self.invalidations.push(kind);
60        }
61    }
62
63    fn request_update(&mut self) {
64        self.update_requested = true;
65    }
66
67    fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
68        self.active_capabilities.push(capabilities);
69    }
70
71    fn pop_active_capabilities(&mut self) {
72        self.active_capabilities.pop();
73    }
74}
75
76#[doc(hidden)]
77pub fn invalidate_all_layout_caches() {
78    crate::render_state::invalidate_layout_cache_epoch();
79}
80
81fn layout_measure_telemetry_threshold_ms() -> Option<f64> {
82    static THRESHOLD_MS: OnceLock<Option<f64>> = OnceLock::new();
83    *THRESHOLD_MS.get_or_init(|| {
84        std::env::var("CRANPOSE_LAYOUT_MEASURE_TELEMETRY_MS")
85            .ok()
86            .and_then(|value| value.parse::<f64>().ok())
87            .filter(|value| value.is_finite() && *value >= 0.0)
88            .or_else(|| {
89                std::env::var_os("CRANPOSE_LAYOUT_MEASURE_TELEMETRY")
90                    .is_some()
91                    .then_some(4.0)
92            })
93    })
94}
95
96struct LayoutMeasureTelemetry {
97    root: NodeId,
98    start: Instant,
99    after_repasses: Instant,
100    after_guard: Instant,
101    after_builder: Instant,
102    after_measure: Instant,
103    after_root_place: Instant,
104    after_aux: Instant,
105    after_builder_drop: Instant,
106    after_guard_drop: Instant,
107}
108
109fn log_layout_measure_telemetry(times: LayoutMeasureTelemetry) {
110    let Some(threshold_ms) = layout_measure_telemetry_threshold_ms() else {
111        return;
112    };
113
114    let total_ms = times
115        .after_guard_drop
116        .duration_since(times.start)
117        .as_secs_f64()
118        * 1000.0;
119    if total_ms < threshold_ms {
120        return;
121    }
122
123    let repass_ms = times
124        .after_repasses
125        .duration_since(times.start)
126        .as_secs_f64()
127        * 1000.0;
128    let guard_ms = times
129        .after_guard
130        .duration_since(times.after_repasses)
131        .as_secs_f64()
132        * 1000.0;
133    let builder_ms = times
134        .after_builder
135        .duration_since(times.after_guard)
136        .as_secs_f64()
137        * 1000.0;
138    let measure_ms = times
139        .after_measure
140        .duration_since(times.after_builder)
141        .as_secs_f64()
142        * 1000.0;
143    let root_place_ms = times
144        .after_root_place
145        .duration_since(times.after_measure)
146        .as_secs_f64()
147        * 1000.0;
148    let aux_ms = times
149        .after_aux
150        .duration_since(times.after_root_place)
151        .as_secs_f64()
152        * 1000.0;
153    let builder_drop_ms = times
154        .after_builder_drop
155        .duration_since(times.after_aux)
156        .as_secs_f64()
157        * 1000.0;
158    let guard_drop_ms = times
159        .after_guard_drop
160        .duration_since(times.after_builder_drop)
161        .as_secs_f64()
162        * 1000.0;
163    log::warn!(
164        "[layout-measure-telemetry] root={} total_ms={total_ms:.2} repass_ms={repass_ms:.2} guard_ms={guard_ms:.2} builder_ms={builder_ms:.2} measure_ms={measure_ms:.2} root_place_ms={root_place_ms:.2} aux_ms={aux_ms:.2} builder_drop_ms={builder_drop_ms:.2} guard_drop_ms={guard_drop_ms:.2}",
165        times.root
166    );
167}
168
169fn log_node_measure_telemetry(
170    kind: &'static str,
171    node_id: NodeId,
172    constraints: Constraints,
173    size: Size,
174    children: usize,
175    start: Instant,
176) {
177    let Some(threshold_ms) = layout_measure_telemetry_threshold_ms() else {
178        return;
179    };
180
181    let total_ms = start.elapsed().as_secs_f64() * 1000.0;
182    if total_ms < threshold_ms {
183        return;
184    }
185
186    log::warn!(
187        "[layout-node-telemetry] kind={kind} node={} total_ms={total_ms:.2} constraints=({:.1},{:.1},{:.1},{:.1}) size=({:.1},{:.1}) children={children}",
188        node_id,
189        constraints.min_width,
190        constraints.max_width,
191        constraints.min_height,
192        constraints.max_height,
193        size.width,
194        size.height,
195    );
196}
197
198struct ApplierSlotGuard<'a> {
199    target: &'a mut MemoryApplier,
200    host: Rc<ConcreteApplierHost<MemoryApplier>>,
201    slots: Rc<RefCell<SlotTable>>,
202}
203
204impl<'a> ApplierSlotGuard<'a> {
205    fn new(target: &'a mut MemoryApplier) -> Self {
206        let original_applier = std::mem::replace(target, MemoryApplier::new());
207        let host = Rc::new(ConcreteApplierHost::new(original_applier));
208
209        let slots = {
210            let mut applier_ref = host.borrow_typed();
211            std::mem::take(applier_ref.slots())
212        };
213        let slots = Rc::new(RefCell::new(slots));
214
215        Self {
216            target,
217            host,
218            slots,
219        }
220    }
221
222    fn host(&self) -> Rc<ConcreteApplierHost<MemoryApplier>> {
223        Rc::clone(&self.host)
224    }
225
226    fn slots_handle(&self) -> Rc<RefCell<SlotTable>> {
227        Rc::clone(&self.slots)
228    }
229}
230
231impl Drop for ApplierSlotGuard<'_> {
232    fn drop(&mut self) {
233        {
234            let mut applier_ref = self.host.borrow_typed();
235            *applier_ref.slots() = std::mem::take(&mut *self.slots.borrow_mut());
236        }
237
238        {
239            let mut applier_ref = self.host.borrow_typed();
240            let original_applier = std::mem::take(&mut *applier_ref);
241            let _ = std::mem::replace(self.target, original_applier);
242        }
243    }
244}
245
246struct ModifierChainMeasurement {
247    size: Size,
248    content_offset: Point,
249    offset: Point,
250}
251
252type LayoutModifierNodeData = (
253    usize,
254    Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
255);
256
257struct ScratchVecPool<T> {
258    available: Vec<Vec<T>>,
259}
260
261impl<T> ScratchVecPool<T> {
262    fn acquire(&mut self) -> Vec<T> {
263        self.available.pop().unwrap_or_default()
264    }
265
266    fn release(&mut self, mut values: Vec<T>) {
267        values.clear();
268        self.available.push(values);
269    }
270
271    #[cfg(test)]
272    fn available_count(&self) -> usize {
273        self.available.len()
274    }
275}
276
277impl<T> Default for ScratchVecPool<T> {
278    fn default() -> Self {
279        Self {
280            available: Vec::new(),
281        }
282    }
283}
284
285#[derive(Default)]
286pub(crate) struct FrameLayoutArena {
287    tmp_records: ScratchVecPool<(NodeId, ChildRecord)>,
288    tmp_child_ids: ScratchVecPool<NodeId>,
289    tmp_layout_node_data: ScratchVecPool<LayoutModifierNodeData>,
290    tmp_placements: ScratchVecPool<Placement>,
291}
292
293#[cfg(test)]
294impl FrameLayoutArena {
295    pub(crate) fn available_placement_scratch_count(&self) -> usize {
296        self.tmp_placements.available_count()
297    }
298
299    pub(crate) fn seed_placement_scratch_for_test(&mut self) {
300        self.tmp_placements.release(Vec::with_capacity(1));
301    }
302}
303
304/// Discrete event callback reference produced during semantics extraction.
305#[derive(Clone, Debug, PartialEq, Eq)]
306pub struct SemanticsCallback {
307    node_id: NodeId,
308}
309
310impl SemanticsCallback {
311    pub fn new(node_id: NodeId) -> Self {
312        Self { node_id }
313    }
314
315    pub fn node_id(&self) -> NodeId {
316        self.node_id
317    }
318}
319
320/// Semantics action exposed to the input system.
321#[derive(Clone, Debug, PartialEq, Eq)]
322pub enum SemanticsAction {
323    Click { handler: SemanticsCallback },
324}
325
326/// Semantic role describing how a node should participate in accessibility and hit testing.
327/// Roles are now derived from SemanticsConfiguration rather than widget types.
328#[derive(Clone, Debug, PartialEq, Eq)]
329pub enum SemanticsRole {
330    /// Generic container or layout node
331    Layout,
332    /// Subcomposition boundary
333    Subcompose,
334    /// Text content derived from the text node semantics payload.
335    Text { value: String },
336    /// Spacer (non-interactive)
337    Spacer,
338    /// Button (derived from the semantics `role`)
339    Button,
340    /// Unknown or unspecified role
341    Unknown,
342}
343
344/// A single node within the semantics tree.
345///
346/// Not `Eq`: a node now carries drawn-control bounds, which are floats. The
347/// tree is compared for "did anything a screen reader can see change", and
348/// that comparison is `PartialEq` everywhere else on the accessibility path
349/// (`AccessibilityRect`, `AccessibilityElement`) for the same reason.
350#[derive(Clone, Debug, PartialEq)]
351pub struct SemanticsNode {
352    pub node_id: NodeId,
353    /// Where this node sits in the tree (layout, text, subcomposition …).
354    pub role: SemanticsRole,
355    /// What kind of control this node is, as a screen reader announces it —
356    /// Compose's `Role`. Separate from [`SemanticsNode::role`] because a
357    /// clickable `Row` is structurally a layout node and semantically a button.
358    pub widget_role: Option<SemanticsWidgetRole>,
359    pub actions: Vec<SemanticsAction>,
360    pub children: Vec<SemanticsNode>,
361    pub description: Option<String>,
362    pub state_description: Option<String>,
363    pub on_click_label: Option<String>,
364    /// What this control does when a screen reader asks for its long press.
365    pub on_long_click: Option<SemanticsLongClick>,
366    /// What the long press does, as a verb phrase a reader reads out.
367    pub on_long_click_label: Option<String>,
368    pub selected: Option<bool>,
369    pub toggled: Option<bool>,
370    pub enabled: bool,
371    pub custom_actions: Vec<SemanticsCustomAction>,
372    /// Controls this node drew rather than laid out; their bounds are relative
373    /// to this node's own top-left. See [`CanvasSemanticsNode`].
374    pub canvas_children: Vec<CanvasSemanticsNode>,
375    pub editable_text: bool,
376    /// Whether a screen reader skips this node and everything under it.
377    pub hidden: bool,
378    /// Whether a screen reader takes this node and the text under it as one
379    /// stop.
380    pub merge_descendants: bool,
381    /// Whether the selectable controls under this node form one group.
382    pub selectable_group: bool,
383    /// The title of the screen or pane this node is the root of.
384    pub pane_title: Option<String>,
385    /// Why the control's content is wrong, when it is.
386    pub error: Option<String>,
387    /// Whether this field holds a secret, so its text stays unspoken.
388    pub password: bool,
389    /// Where a screen reader visits this node among the ones beside it.
390    pub traversal_index: f32,
391    /// The text an editable field holds.
392    pub text: Option<String>,
393    pub text_selection: Option<TextRange>,
394    /// Whether this node registered a focus target, so focus can land on it.
395    /// Compose's `SemanticsProperties.Focused` companion.
396    pub focusable: bool,
397    /// Whether focus sits on this node right now.
398    pub focused: bool,
399    /// How urgently a screen reader reads this node when its text changes.
400    /// Compose's `SemanticsProperties.LiveRegion`.
401    pub live_region: Option<LiveRegionMode>,
402    /// The value this control holds inside a range. Compose's
403    /// `ProgressBarRangeInfo`.
404    pub progress: Option<ProgressBarRangeInfo>,
405    /// What this control does when a screen reader moves its value.
406    pub set_progress: Option<SemanticsSetProgress>,
407    /// What this field does when a screen reader hands it text.
408    pub set_text: Option<SemanticsSetText>,
409    /// What this control does when a screen reader asks it to open.
410    pub expand: Option<SemanticsExpand>,
411    /// What this control does when a screen reader asks it to close.
412    pub collapse: Option<SemanticsExpand>,
413    /// What this control does when a screen reader asks to send it away.
414    pub dismiss: Option<SemanticsDismiss>,
415    /// How far this container scrolled up and down, when it scrolls.
416    pub vertical_scroll: Option<ScrollAxisRange>,
417    /// How far this container scrolled left and right, when it scrolls.
418    pub horizontal_scroll: Option<ScrollAxisRange>,
419    /// What this container does when a screen reader pages it.
420    pub scroll_by: Option<SemanticsScrollBy>,
421    /// What this list does when a screen reader asks for the row at an index.
422    pub scroll_to_index: Option<SemanticsScrollToIndex>,
423    /// How many rows and columns this list holds, when it is a list.
424    pub collection: Option<CollectionInfo>,
425}
426
427impl Default for SemanticsNode {
428    fn default() -> Self {
429        Self {
430            node_id: 0,
431            role: SemanticsRole::Unknown,
432            widget_role: None,
433            actions: Vec::new(),
434            children: Vec::new(),
435            description: None,
436            state_description: None,
437            on_click_label: None,
438            on_long_click: None,
439            on_long_click_label: None,
440            selected: None,
441            toggled: None,
442            enabled: true,
443            custom_actions: Vec::new(),
444            canvas_children: Vec::new(),
445            editable_text: false,
446            hidden: false,
447            merge_descendants: false,
448            selectable_group: false,
449            pane_title: None,
450            error: None,
451            password: false,
452            traversal_index: 0.0,
453            text: None,
454            text_selection: None,
455            focusable: false,
456            focused: false,
457            live_region: None,
458            progress: None,
459            set_progress: None,
460            set_text: None,
461            expand: None,
462            collapse: None,
463            dismiss: None,
464            vertical_scroll: None,
465            horizontal_scroll: None,
466            scroll_by: None,
467            scroll_to_index: None,
468            collection: None,
469        }
470    }
471}
472
473/// Rooted semantics tree extracted after layout.
474#[derive(Clone, Debug, PartialEq)]
475pub struct SemanticsTree {
476    root: SemanticsNode,
477}
478
479impl SemanticsTree {
480    fn new(root: SemanticsNode) -> Self {
481        Self { root }
482    }
483
484    pub fn root(&self) -> &SemanticsNode {
485        &self.root
486    }
487}
488
489#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
490pub struct LayoutAllocationDebugStats {
491    pub layout_box_count: usize,
492    pub layout_box_child_count: usize,
493    pub layout_box_child_capacity: usize,
494    pub layout_box_heap_bytes: usize,
495    pub modifier_slice_count: usize,
496    pub modifier_slice_heap_bytes: usize,
497    pub modifier_draw_command_count: usize,
498    pub modifier_draw_command_capacity: usize,
499    pub modifier_pointer_input_count: usize,
500    pub modifier_pointer_input_capacity: usize,
501    pub modifier_click_handler_count: usize,
502    pub modifier_click_handler_capacity: usize,
503    pub modifier_text_content_count: usize,
504    pub modifier_text_style_count: usize,
505    pub modifier_text_layout_options_count: usize,
506    pub modifier_prepared_text_layout_count: usize,
507    pub modifier_graphics_layer_count: usize,
508    pub modifier_graphics_layer_resolver_count: usize,
509    pub semantics_node_count: usize,
510    pub semantics_action_count: usize,
511    pub semantics_action_capacity: usize,
512    pub semantics_child_count: usize,
513    pub semantics_child_capacity: usize,
514    pub semantics_description_count: usize,
515    pub semantics_description_bytes: usize,
516    pub semantics_text_role_bytes: usize,
517    pub semantics_heap_bytes: usize,
518}
519
520impl LayoutAllocationDebugStats {
521    fn add_modifier_slice(&mut self, stats: ModifierNodeSlicesDebugStats) {
522        self.modifier_slice_count += 1;
523        self.modifier_slice_heap_bytes += stats.heap_bytes;
524        self.modifier_draw_command_count += stats.draw_command_count;
525        self.modifier_draw_command_capacity += stats.draw_command_capacity;
526        self.modifier_pointer_input_count += stats.pointer_input_count;
527        self.modifier_pointer_input_capacity += stats.pointer_input_capacity;
528        self.modifier_click_handler_count += stats.click_handler_count;
529        self.modifier_click_handler_capacity += stats.click_handler_capacity;
530        self.modifier_text_content_count += usize::from(stats.has_text_content);
531        self.modifier_text_style_count += usize::from(stats.has_text_style);
532        self.modifier_text_layout_options_count += usize::from(stats.has_text_layout_options);
533        self.modifier_prepared_text_layout_count += usize::from(stats.has_prepared_text_layout);
534        self.modifier_graphics_layer_count += usize::from(stats.has_graphics_layer);
535        self.modifier_graphics_layer_resolver_count +=
536            usize::from(stats.has_graphics_layer_resolver);
537    }
538}
539
540/// Result of running layout for a Compose tree.
541#[derive(Debug, Clone)]
542pub struct LayoutTree {
543    root: LayoutBox,
544}
545
546impl LayoutTree {
547    pub fn new(root: LayoutBox) -> Self {
548        Self { root }
549    }
550
551    pub fn root(&self) -> &LayoutBox {
552        &self.root
553    }
554
555    pub fn root_mut(&mut self) -> &mut LayoutBox {
556        &mut self.root
557    }
558
559    pub fn into_root(self) -> LayoutBox {
560        self.root
561    }
562
563    pub fn debug_allocation_stats(&self) -> LayoutAllocationDebugStats {
564        let mut stats = LayoutAllocationDebugStats::default();
565        record_layout_box_allocation_stats(&self.root, &mut stats);
566        stats
567    }
568}
569
570/// Layout information for a single node.
571#[derive(Debug, Clone)]
572pub struct LayoutBox {
573    pub node_id: NodeId,
574    pub rect: GeometryRect,
575    /// Content offset for scroll/inner transforms (applies to children, NOT this node's position)
576    pub content_offset: Point,
577    pub node_data: LayoutNodeData,
578    pub children: Vec<LayoutBox>,
579}
580
581impl LayoutBox {
582    pub fn new(
583        node_id: NodeId,
584        rect: GeometryRect,
585        content_offset: Point,
586        node_data: LayoutNodeData,
587        children: Vec<LayoutBox>,
588    ) -> Self {
589        Self {
590            node_id,
591            rect,
592            content_offset,
593            node_data,
594            children,
595        }
596    }
597}
598
599/// Snapshot of the data required to render a layout node.
600#[derive(Debug, Clone)]
601pub struct LayoutNodeData {
602    pub modifier: Modifier,
603    pub resolved_modifiers: ResolvedModifiers,
604    pub modifier_slices: Rc<ModifierNodeSlices>,
605    pub kind: LayoutNodeKind,
606}
607
608impl LayoutNodeData {
609    pub fn new(
610        modifier: Modifier,
611        resolved_modifiers: ResolvedModifiers,
612        modifier_slices: Rc<ModifierNodeSlices>,
613        kind: LayoutNodeKind,
614    ) -> Self {
615        Self {
616            modifier,
617            resolved_modifiers,
618            modifier_slices,
619            kind,
620        }
621    }
622
623    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
624        self.resolved_modifiers
625    }
626
627    pub fn modifier_slices(&self) -> &ModifierNodeSlices {
628        &self.modifier_slices
629    }
630}
631
632/// Classification of the node captured inside a [`LayoutBox`].
633///
634/// Note: Text content is no longer represented as a distinct LayoutNodeKind.
635/// Text nodes now use `LayoutNodeKind::Layout` with their content stored in
636/// `modifier_slices.text_content()` via TextModifierNode, following Jetpack
637/// Compose's pattern where text is a modifier node capability.
638#[derive(Clone)]
639pub enum LayoutNodeKind {
640    Layout,
641    Subcompose,
642    Spacer,
643    Button { on_click: Rc<RefCell<dyn FnMut()>> },
644    Unknown,
645}
646
647impl fmt::Debug for LayoutNodeKind {
648    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
649        match self {
650            LayoutNodeKind::Layout => f.write_str("Layout"),
651            LayoutNodeKind::Subcompose => f.write_str("Subcompose"),
652            LayoutNodeKind::Spacer => f.write_str("Spacer"),
653            LayoutNodeKind::Button { .. } => f.write_str("Button"),
654            LayoutNodeKind::Unknown => f.write_str("Unknown"),
655        }
656    }
657}
658
659/// Extension trait that equips `MemoryApplier` with layout computation.
660pub trait LayoutEngine {
661    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError>;
662}
663
664impl LayoutEngine for MemoryApplier {
665    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError> {
666        let measurements = measure_layout(self, root, max_size)?;
667        measurements
668            .into_layout_tree()
669            .ok_or(NodeError::MissingContext {
670                id: root,
671                reason: "layout tree was not requested",
672            })
673    }
674}
675
676/// Result of running the measure pass for a Compose layout tree.
677#[derive(Debug, Clone)]
678pub struct LayoutMeasurements {
679    root: Rc<MeasuredNode>,
680    semantics: Option<SemanticsTree>,
681    layout_tree: Option<LayoutTree>,
682}
683
684impl LayoutMeasurements {
685    fn new(
686        root: Rc<MeasuredNode>,
687        semantics: Option<SemanticsTree>,
688        layout_tree: Option<LayoutTree>,
689    ) -> Self {
690        Self {
691            root,
692            semantics,
693            layout_tree,
694        }
695    }
696
697    /// Returns the measured size of the root node.
698    pub fn root_size(&self) -> Size {
699        self.root.size
700    }
701
702    pub fn semantics_tree(&self) -> Option<&SemanticsTree> {
703        self.semantics.as_ref()
704    }
705
706    pub fn debug_allocation_stats(&self) -> LayoutAllocationDebugStats {
707        let mut stats = self
708            .layout_tree
709            .as_ref()
710            .map(LayoutTree::debug_allocation_stats)
711            .unwrap_or_default();
712        if let Some(semantics) = &self.semantics {
713            record_semantics_allocation_stats(semantics.root(), &mut stats);
714        }
715        stats
716    }
717
718    /// Consumes the measurements and returns the built [`LayoutTree`], if requested.
719    pub fn into_layout_tree(self) -> Option<LayoutTree> {
720        self.layout_tree
721    }
722
723    /// Returns a cloned [`LayoutTree`] for rendering/debug consumers, if requested.
724    pub fn layout_tree(&self) -> Option<LayoutTree> {
725        self.layout_tree.clone()
726    }
727}
728
729/// Builds a semantics tree from an existing [`LayoutTree`].
730///
731/// This is useful for consumers that need semantics on demand without forcing
732/// every layout pass to eagerly allocate a full [`SemanticsTree`].
733pub fn build_semantics_tree_from_layout_tree(layout_tree: &LayoutTree) -> SemanticsTree {
734    SemanticsTree::new(build_semantics_node_from_layout_box(layout_tree.root()))
735}
736
737/// Builds a layout snapshot from retained layout state in the live applier tree.
738///
739/// Renderers use retained node state directly. This function exists for debug,
740/// robot, and tests that need an owned [`LayoutTree`] without forcing every
741/// layout pass to allocate one.
742pub fn build_layout_tree_from_applier(
743    applier: &mut MemoryApplier,
744    root: NodeId,
745) -> Result<Option<LayoutTree>, NodeError> {
746    fn snapshot(
747        applier: &mut MemoryApplier,
748        node_id: NodeId,
749    ) -> Result<Option<(crate::widgets::nodes::layout_node::LayoutState, Vec<NodeId>)>, NodeError>
750    {
751        match applier.with_node::<LayoutNode, _>(node_id, |node| {
752            (node.layout_state(), node.children.clone())
753        }) {
754            Ok(snapshot) => return Ok(Some(snapshot)),
755            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
756            Err(err) => return Err(err),
757        }
758
759        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
760            (node.layout_state(), node.active_children())
761        }) {
762            Ok(snapshot) => Ok(Some(snapshot)),
763            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
764            Err(err) => Err(err),
765        }
766    }
767
768    fn place(
769        applier: &mut MemoryApplier,
770        node_id: NodeId,
771        parent_content_origin: Point,
772        parent_layer_translation: Point,
773    ) -> Result<Option<LayoutBox>, NodeError> {
774        let Some((state, child_ids)) = snapshot(applier, node_id)? else {
775            return Ok(None);
776        };
777        if !state.is_placed() {
778            return Ok(None);
779        }
780
781        let top_left = Point {
782            x: parent_content_origin.x + state.position().x,
783            y: parent_content_origin.y + state.position().y,
784        };
785        let rect = GeometryRect {
786            x: top_left.x,
787            y: top_left.y,
788            width: state.size().width,
789            height: state.size().height,
790        };
791        let info = runtime_metadata_for(applier, node_id)?;
792        let kind = layout_kind_from_metadata(node_id, &info);
793        let RuntimeNodeMetadata {
794            modifier,
795            resolved_modifiers,
796            modifier_slices,
797            ..
798        } = info;
799
800        let layer_translation = match modifier_slices.graphics_layer() {
801            Some(layer) => Point {
802                x: parent_layer_translation.x + layer.translation_x,
803                y: parent_layer_translation.y + layer.translation_y,
804            },
805            None => parent_layer_translation,
806        };
807
808        if let Some(sink) = modifier_slices.text_field_window_origin() {
809            sink.set(Point {
810                x: top_left.x + layer_translation.x,
811                y: top_left.y + layer_translation.y,
812            });
813        }
814
815        if let Some(sink) = modifier_slices.viewport_window_rect() {
816            sink.set(GeometryRect {
817                x: top_left.x + layer_translation.x,
818                y: top_left.y + layer_translation.y,
819                width: state.size().width,
820                height: state.size().height,
821            });
822        }
823
824        modifier_slices.publish_pointer_input_size(state.size());
825
826        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
827        let child_origin = Point {
828            x: top_left.x + state.content_offset.x,
829            y: top_left.y + state.content_offset.y,
830        };
831        let mut children = Vec::with_capacity(child_ids.len());
832        for child_id in child_ids {
833            if let Some(child) = place(applier, child_id, child_origin, layer_translation)? {
834                children.push(child);
835            }
836        }
837
838        Ok(Some(LayoutBox::new(
839            node_id,
840            rect,
841            state.content_offset,
842            data,
843            children,
844        )))
845    }
846
847    place(applier, root, Point::default(), Point::default()).map(|root| root.map(LayoutTree::new))
848}
849
850/// Builds a semantics snapshot from retained layout state in the live applier tree.
851///
852/// This is the on-demand counterpart to [`build_layout_tree_from_applier`].
853/// It follows the currently placed child set, including subcompose active
854/// children, and clears semantics dirty flags for nodes it visits.
855pub fn build_semantics_tree_from_applier(
856    applier: &mut MemoryApplier,
857    root: NodeId,
858) -> Result<Option<SemanticsTree>, NodeError> {
859    fn node(
860        applier: &mut MemoryApplier,
861        node_id: NodeId,
862    ) -> Result<Option<SemanticsNode>, NodeError> {
863        match applier.with_node::<LayoutNode, _>(node_id, |layout| {
864            let state = layout.layout_state();
865            if !state.is_placed() {
866                return None;
867            }
868            let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
869            let config = layout.semantics_configuration();
870            let children = layout.children.clone();
871            layout.clear_needs_semantics();
872            Some((role, config, children))
873        }) {
874            Ok(Some((role, config, child_ids))) => {
875                let mut children = Vec::with_capacity(child_ids.len());
876                for child_id in child_ids {
877                    if let Some(child) = node(applier, child_id)? {
878                        children.push(child);
879                    }
880                }
881                return Ok(Some(semantics_node_from_parts(
882                    node_id, role, config, children,
883                )));
884            }
885            Ok(None) => return Ok(None),
886            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
887            Err(err) => return Err(err),
888        }
889
890        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |subcompose| {
891            let state = subcompose.layout_state();
892            if !state.is_placed() {
893                return None;
894            }
895            let config = collect_semantics_from_modifier(&subcompose.modifier());
896            let children = subcompose.active_children();
897            subcompose.clear_needs_semantics();
898            Some((config, children))
899        }) {
900            Ok(Some((config, child_ids))) => {
901                let mut children = Vec::with_capacity(child_ids.len());
902                for child_id in child_ids {
903                    if let Some(child) = node(applier, child_id)? {
904                        children.push(child);
905                    }
906                }
907                Ok(Some(semantics_node_from_parts(
908                    node_id,
909                    SemanticsRole::Subcompose,
910                    config,
911                    children,
912                )))
913            }
914            Ok(None) | Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
915                Ok(None)
916            }
917            Err(err) => Err(err),
918        }
919    }
920
921    node(applier, root).map(|root| root.map(SemanticsTree::new))
922}
923
924#[derive(Clone, Copy, Debug, PartialEq, Eq)]
925pub struct MeasureLayoutOptions {
926    pub collect_semantics: bool,
927    pub build_layout_tree: bool,
928}
929
930impl Default for MeasureLayoutOptions {
931    fn default() -> Self {
932        Self {
933            collect_semantics: true,
934            build_layout_tree: true,
935        }
936    }
937}
938
939/// Check if a node or any of its descendants needs measure (selective measure optimization).
940/// This can be used by the app shell to skip layout when the tree is clean.
941///
942/// O(1) check - just looks at root's dirty flag.
943/// Works because all mutation paths bubble dirty flags to root via composer commands.
944///
945/// Returns Result to force caller to handle errors explicitly. No more unwrap_or(true) safety net.
946pub fn tree_needs_layout(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
947    Ok(applier.get_mut(root)?.needs_layout())
948}
949
950/// Check if the root semantics snapshot is dirty.
951///
952/// Semantics invalidations bubble to the root the same way layout invalidations do,
953/// so a root check is sufficient to determine whether the next layout pass needs to
954/// rebuild semantic data even when geometry is otherwise unchanged.
955pub fn tree_needs_semantics(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
956    Ok(applier.get_mut(root)?.needs_semantics())
957}
958
959#[cfg(test)]
960pub(crate) fn bubble_layout_dirty(applier: &mut MemoryApplier, node_id: NodeId) {
961    cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
962}
963
964/// Runs the measure phase for the subtree rooted at `root`.
965pub fn measure_layout(
966    applier: &mut MemoryApplier,
967    root: NodeId,
968    max_size: Size,
969) -> Result<LayoutMeasurements, NodeError> {
970    measure_layout_with_options(applier, root, max_size, MeasureLayoutOptions::default())
971}
972
973pub fn measure_layout_with_options(
974    applier: &mut MemoryApplier,
975    root: NodeId,
976    max_size: Size,
977    options: MeasureLayoutOptions,
978) -> Result<LayoutMeasurements, NodeError> {
979    let telemetry_start = Instant::now();
980    process_pending_layout_repasses(applier, root)?;
981    let after_repasses = Instant::now();
982
983    let constraints = Constraints {
984        min_width: 0.0,
985        max_width: max_size.width,
986        min_height: 0.0,
987        max_height: max_size.height,
988    };
989
990    let (needs_remeasure, _needs_semantics, cached_epoch) = match applier
991        .with_node::<LayoutNode, _>(root, |node| {
992            (
993                node.needs_measure(),
994                node.needs_semantics(),
995                node.cache_handles().epoch(),
996            )
997        }) {
998        Ok(tuple) => tuple,
999        Err(NodeError::TypeMismatch { .. }) => {
1000            let node = applier.get_mut(root)?;
1001            let measure_dirty = node.needs_measure();
1002            let semantics_dirty = node.needs_semantics();
1003            (measure_dirty, semantics_dirty, 0)
1004        }
1005        Err(err) => return Err(err),
1006    };
1007
1008    let epoch = if needs_remeasure {
1009        crate::render_state::next_layout_cache_epoch()
1010    } else if cached_epoch != 0 {
1011        cached_epoch
1012    } else {
1013        crate::render_state::current_layout_cache_epoch()
1014    };
1015
1016    let guard = ApplierSlotGuard::new(applier);
1017    let applier_host = guard.host();
1018    let slots_handle = guard.slots_handle();
1019    let after_guard = Instant::now();
1020
1021    let frame_arena = crate::render_state::take_layout_frame_arena();
1022    let mut builder = LayoutBuilder::new_with_epoch(
1023        Rc::clone(&applier_host),
1024        epoch,
1025        Rc::clone(&slots_handle),
1026        frame_arena,
1027    );
1028    let after_builder = Instant::now();
1029
1030    let measured = builder.measure_node(root, normalize_constraints(constraints))?;
1031    let after_measure = Instant::now();
1032
1033    if let Ok(mut applier) = applier_host.try_borrow_typed()
1034        && applier
1035            .with_node::<LayoutNode, _>(root, |node| {
1036                node.set_position(Point::default());
1037            })
1038            .is_err()
1039    {
1040        let _ = applier.with_node::<SubcomposeLayoutNode, _>(root, |node| {
1041            node.set_position(Point::default());
1042        });
1043    }
1044    let after_root_place = Instant::now();
1045
1046    let (layout_tree, semantics) = {
1047        let mut applier_ref = applier_host.borrow_typed();
1048        let layout_tree = if options.build_layout_tree {
1049            Some(build_layout_tree(&mut applier_ref, &measured)?)
1050        } else {
1051            None
1052        };
1053        let semantics = if options.collect_semantics {
1054            let semantics_tree = if let Some(layout_tree) = layout_tree.as_ref() {
1055                clear_semantics_dirty_flags(&mut applier_ref, &measured)?;
1056                build_semantics_tree_from_layout_tree(layout_tree)
1057            } else {
1058                build_semantics_tree_from_live_nodes(&mut applier_ref, &measured)?
1059            };
1060            Some(semantics_tree)
1061        } else {
1062            None
1063        };
1064        (layout_tree, semantics)
1065    };
1066    let after_aux = Instant::now();
1067
1068    drop(builder);
1069    let after_builder_drop = Instant::now();
1070
1071    drop(guard);
1072    let after_guard_drop = Instant::now();
1073
1074    log_layout_measure_telemetry(LayoutMeasureTelemetry {
1075        root,
1076        start: telemetry_start,
1077        after_repasses,
1078        after_guard,
1079        after_builder,
1080        after_measure,
1081        after_root_place,
1082        after_aux,
1083        after_builder_drop,
1084        after_guard_drop,
1085    });
1086
1087    Ok(LayoutMeasurements::new(measured, semantics, layout_tree))
1088}
1089
1090fn process_pending_layout_repasses(
1091    applier: &mut MemoryApplier,
1092    root: NodeId,
1093) -> Result<(), NodeError> {
1094    for node_id in crate::render_state::take_modifier_slice_repass_nodes() {
1095        if let Ok(node) = applier.get_mut(node_id) {
1096            let any = node.as_any_mut();
1097            if let Some(layout) = any.downcast_mut::<crate::widgets::nodes::LayoutNode>() {
1098                layout.mark_modifier_slices_dirty();
1099            } else if let Some(subcompose) =
1100                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1101            {
1102                subcompose.mark_modifier_slices_dirty();
1103            }
1104        }
1105    }
1106    let measure_repass_nodes = crate::take_measure_repass_nodes();
1107    let repass_nodes = crate::take_layout_repass_nodes();
1108    if measure_repass_nodes.is_empty() && repass_nodes.is_empty() {
1109        return Ok(());
1110    }
1111    for node_id in measure_repass_nodes {
1112        cranpose_core::bubble_measure_dirty(applier as &mut dyn Applier, node_id);
1113    }
1114    for node_id in repass_nodes {
1115        cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
1116    }
1117    applier.get_mut(root)?.mark_needs_layout();
1118    Ok(())
1119}
1120
1121struct LayoutBuilder {
1122    state: Rc<RefCell<LayoutBuilderState>>,
1123}
1124
1125impl LayoutBuilder {
1126    fn new_with_epoch(
1127        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1128        epoch: u64,
1129        slots: Rc<RefCell<SlotTable>>,
1130        frame_arena: FrameLayoutArena,
1131    ) -> Self {
1132        Self {
1133            state: Rc::new(RefCell::new(LayoutBuilderState::new_with_epoch(
1134                applier,
1135                epoch,
1136                slots,
1137                frame_arena,
1138            ))),
1139        }
1140    }
1141
1142    fn measure_node(
1143        &mut self,
1144        node_id: NodeId,
1145        constraints: Constraints,
1146    ) -> Result<Rc<MeasuredNode>, NodeError> {
1147        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
1148    }
1149
1150    fn set_runtime_handle(&mut self, handle: Option<RuntimeHandle>) {
1151        self.state.borrow_mut().runtime_handle = handle;
1152    }
1153}
1154
1155impl Drop for LayoutBuilder {
1156    fn drop(&mut self) {
1157        if Rc::strong_count(&self.state) != 1 {
1158            return;
1159        }
1160        let Ok(mut state) = self.state.try_borrow_mut() else {
1161            return;
1162        };
1163        crate::render_state::replace_layout_frame_arena(std::mem::take(&mut state.frame_arena));
1164    }
1165}
1166
1167struct LayoutBuilderState {
1168    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1169    runtime_handle: Option<RuntimeHandle>,
1170    slots: Rc<RefCell<SlotTable>>,
1171    cache_epoch: u64,
1172    frame_arena: FrameLayoutArena,
1173}
1174
1175struct LayoutRuntimeFrameBindingCleanup {
1176    state: Rc<RefCell<LayoutRuntimeState>>,
1177}
1178
1179impl LayoutRuntimeFrameBindingCleanup {
1180    fn new(state: Rc<RefCell<LayoutRuntimeState>>) -> Self {
1181        Self { state }
1182    }
1183}
1184
1185impl Drop for LayoutRuntimeFrameBindingCleanup {
1186    fn drop(&mut self) {
1187        self.state.borrow().clear_frame_bindings();
1188    }
1189}
1190
1191impl LayoutBuilderState {
1192    fn new_with_epoch(
1193        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1194        epoch: u64,
1195        slots: Rc<RefCell<SlotTable>>,
1196        frame_arena: FrameLayoutArena,
1197    ) -> Self {
1198        let runtime_handle = applier.borrow_typed().runtime_handle();
1199
1200        Self {
1201            applier,
1202            runtime_handle,
1203            slots,
1204            cache_epoch: epoch,
1205            frame_arena,
1206        }
1207    }
1208
1209    fn try_with_applier_result<R>(
1210        state_rc: &Rc<RefCell<Self>>,
1211        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
1212    ) -> Option<Result<R, NodeError>> {
1213        let host = {
1214            let state = state_rc.borrow();
1215            Rc::clone(&state.applier)
1216        };
1217
1218        let Ok(mut applier) = host.try_borrow_typed() else {
1219            return None;
1220        };
1221
1222        Some(f(&mut applier))
1223    }
1224
1225    fn with_applier_result<R>(
1226        state_rc: &Rc<RefCell<Self>>,
1227        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
1228    ) -> Result<R, NodeError> {
1229        Self::try_with_applier_result(state_rc, f).unwrap_or_else(|| {
1230            Err(NodeError::MissingContext {
1231                id: NodeId::default(),
1232                reason: "applier already borrowed",
1233            })
1234        })
1235    }
1236
1237    fn clear_node_placed(state_rc: &Rc<RefCell<Self>>, node_id: NodeId) {
1238        let host = {
1239            let state = state_rc.borrow();
1240            Rc::clone(&state.applier)
1241        };
1242        let Ok(mut applier) = host.try_borrow_typed() else {
1243            return;
1244        };
1245        if applier
1246            .with_node::<LayoutNode, _>(node_id, |node| {
1247                node.clear_placed();
1248            })
1249            .is_err()
1250        {
1251            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1252                node.clear_placed();
1253            });
1254        }
1255    }
1256
1257    fn measure_node(
1258        state_rc: Rc<RefCell<Self>>,
1259        node_id: NodeId,
1260        constraints: Constraints,
1261    ) -> Result<Rc<MeasuredNode>, NodeError> {
1262        let telemetry_start = Instant::now();
1263        Self::clear_node_placed(&state_rc, node_id);
1264
1265        if let Some(subcompose) =
1266            Self::try_measure_subcompose(Rc::clone(&state_rc), node_id, constraints)?
1267        {
1268            log_node_measure_telemetry(
1269                "subcompose",
1270                node_id,
1271                constraints,
1272                subcompose.size,
1273                subcompose.children.len(),
1274                telemetry_start,
1275            );
1276            return Ok(subcompose);
1277        }
1278
1279        if let Some(result) = Self::try_with_applier_result(&state_rc, |applier| {
1280            match applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1281                LayoutNodeSnapshot::from_layout_node(layout_node)
1282            }) {
1283                Ok(snapshot) => Ok(Some(snapshot)),
1284                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
1285                Err(err) => Err(err),
1286            }
1287        }) && let Some(snapshot) = result?
1288        {
1289            let measured =
1290                Self::measure_layout_node(Rc::clone(&state_rc), node_id, snapshot, constraints)?;
1291            log_node_measure_telemetry(
1292                "layout",
1293                node_id,
1294                constraints,
1295                measured.size,
1296                measured.children.len(),
1297                telemetry_start,
1298            );
1299            return Ok(measured);
1300        }
1301
1302        let measured = Rc::new(MeasuredNode::new(
1303            node_id,
1304            Size::default(),
1305            Point { x: 0.0, y: 0.0 },
1306            Point::default(),
1307            Vec::new(),
1308        ));
1309        log_node_measure_telemetry(
1310            "fallback",
1311            node_id,
1312            constraints,
1313            measured.size,
1314            measured.children.len(),
1315            telemetry_start,
1316        );
1317        Ok(measured)
1318    }
1319
1320    fn cached_measure_node_with_applier(
1321        applier: &mut MemoryApplier,
1322        node_id: NodeId,
1323        constraints: Constraints,
1324    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1325        let Some(data) = Self::layout_child_measure_data(applier, node_id)? else {
1326            return Ok(None);
1327        };
1328        if data.needs_measure
1329            || data.needs_layout
1330            || data.cache.epoch() == 0
1331            || data.cache.epoch() != crate::render_state::current_layout_cache_epoch()
1332        {
1333            return Ok(None);
1334        }
1335
1336        let Some(measured) = data.cache.get_measurement(constraints) else {
1337            return Ok(None);
1338        };
1339
1340        if let Some(layout_state) = data.layout_state {
1341            let mut layout_state = layout_state.borrow_mut();
1342            layout_state.set_size(measured.size);
1343            layout_state.measurement_constraints = constraints;
1344        } else {
1345            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1346                node.set_measured_size(measured.size);
1347            });
1348        }
1349
1350        Ok(Some(measured))
1351    }
1352
1353    fn try_measure_subcompose(
1354        state_rc: Rc<RefCell<Self>>,
1355        node_id: NodeId,
1356        constraints: Constraints,
1357    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1358        let applier_host = {
1359            let state = state_rc.borrow();
1360            Rc::clone(&state.applier)
1361        };
1362
1363        let (node_handle, resolved_modifiers) = {
1364            let Ok(mut applier) = applier_host.try_borrow_typed() else {
1365                return Ok(None);
1366            };
1367            let node = match applier.get_mut(node_id) {
1368                Ok(node) => node,
1369                Err(NodeError::Missing { .. }) => return Ok(None),
1370                Err(err) => return Err(err),
1371            };
1372            let any = node.as_any_mut();
1373            if let Some(subcompose) =
1374                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1375            {
1376                let handle = subcompose.handle();
1377                let resolved_modifiers = handle.resolved_modifiers();
1378                (handle, resolved_modifiers)
1379            } else {
1380                return Ok(None);
1381            }
1382        };
1383
1384        let runtime_handle = {
1385            let mut state = state_rc.borrow_mut();
1386            if state.runtime_handle.is_none()
1387                && let Ok(applier) = applier_host.try_borrow_typed()
1388            {
1389                state.runtime_handle = applier.runtime_handle();
1390            }
1391            state
1392                .runtime_handle
1393                .clone()
1394                .ok_or(NodeError::MissingContext {
1395                    id: node_id,
1396                    reason: "runtime handle required for subcomposition",
1397                })?
1398        };
1399
1400        let props = resolved_modifiers.layout_properties();
1401        let padding = resolved_modifiers.padding();
1402        let offset = resolved_modifiers.offset();
1403        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
1404
1405        if let DimensionConstraint::Points(width) = props.width() {
1406            let constrained_width = width - padding.horizontal_sum();
1407            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
1408            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
1409        }
1410        if let DimensionConstraint::Points(height) = props.height() {
1411            let constrained_height = height - padding.vertical_sum();
1412            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
1413            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
1414        }
1415
1416        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
1417        let slots_host = slots_guard.host();
1418        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
1419        let observer = SnapshotStateObserver::new(|callback| callback());
1420        let composer = Composer::new(
1421            Rc::clone(&slots_host),
1422            applier_host_dyn,
1423            runtime_handle.clone(),
1424            observer,
1425            Some(node_id),
1426        );
1427        composer.enter_phase(Phase::Measure);
1428
1429        let state_rc_clone = Rc::clone(&state_rc);
1430        let measure_error = RefCell::new(None);
1431        let state_rc_for_subcompose = Rc::clone(&state_rc_clone);
1432        let error_for_subcompose = &measure_error;
1433        let measured_children = node_handle.measured_children_scratch();
1434        let measured_children_for_subcompose = Rc::clone(&measured_children);
1435        let state_rc_for_cached = Rc::clone(&state_rc_clone);
1436        let error_for_cached = &measure_error;
1437        let measured_children_for_cached = Rc::clone(&measured_children);
1438        let measured_children_for_lookup = Rc::clone(&measured_children);
1439        let measured_children_for_retained = Rc::clone(&measured_children);
1440
1441        let measure_result = node_handle.measure_with_cached_batch(
1442            &composer,
1443            node_id,
1444            inner_constraints,
1445            CachedBatchMeasureInputs {
1446                measurer: Box::new(
1447                    move |child_id: NodeId, child_constraints: Constraints| -> Size {
1448                        match Self::measure_node(
1449                            Rc::clone(&state_rc_for_subcompose),
1450                            child_id,
1451                            child_constraints,
1452                        ) {
1453                            Ok(measured) => {
1454                                measured_children_for_subcompose
1455                                    .borrow_mut()
1456                                    .insert(child_id, Rc::clone(&measured));
1457                                measured.size
1458                            }
1459                            Err(err) => {
1460                                let mut slot = error_for_subcompose.borrow_mut();
1461                                if slot.is_none() {
1462                                    *slot = Some(err);
1463                                }
1464                                Size::default()
1465                            }
1466                        }
1467                    },
1468                ),
1469                cached_measure_batch_registrar: Box::new(
1470                    move |child_ids: &[NodeId],
1471                          child_constraints: Constraints,
1472                          out: &mut Vec<Option<Size>>| {
1473                        out.clear();
1474                        out.resize(child_ids.len(), None);
1475
1476                        let applier_host = {
1477                            let state = state_rc_for_cached.borrow();
1478                            Rc::clone(&state.applier)
1479                        };
1480                        let Ok(mut applier) = applier_host.try_borrow_typed() else {
1481                            return;
1482                        };
1483
1484                        let mut measured_children = measured_children_for_cached.borrow_mut();
1485                        for (index, &child_id) in child_ids.iter().enumerate() {
1486                            match Self::cached_measure_node_with_applier(
1487                                &mut applier,
1488                                child_id,
1489                                child_constraints,
1490                            ) {
1491                                Ok(Some(measured)) => {
1492                                    out[index] = Some(measured.size);
1493                                    measured_children.insert(child_id, Rc::clone(&measured));
1494                                }
1495                                Ok(None) => {}
1496                                Err(err) => {
1497                                    let mut slot = error_for_cached.borrow_mut();
1498                                    if slot.is_none() {
1499                                        *slot = Some(err);
1500                                    }
1501                                    break;
1502                                }
1503                            }
1504                        }
1505                    },
1506                ),
1507                retained_measure_lookup: Box::new(move |child_id| {
1508                    measured_children_for_lookup
1509                        .borrow()
1510                        .get(&child_id)
1511                        .cloned()
1512                }),
1513                retained_measure_registrar: Box::new(move |measurements| {
1514                    let mut measured_children = measured_children_for_retained.borrow_mut();
1515                    for measured in measurements {
1516                        measured_children.insert(measured.node_id(), Rc::clone(measured));
1517                    }
1518                }),
1519                error: &measure_error,
1520            },
1521        )?;
1522        drop(composer);
1523        slots_guard.restore(slots_host.into_table()?);
1524
1525        if let Some(err) = measure_error.borrow_mut().take() {
1526            return Err(err);
1527        }
1528
1529        let cranpose_ui_layout::MeasureResult {
1530            size: measured_size,
1531            placements,
1532        } = measure_result;
1533
1534        let mut width = measured_size.width + padding.horizontal_sum();
1535        let mut height = measured_size.height + padding.vertical_sum();
1536
1537        width = resolve_dimension(
1538            width,
1539            props.width(),
1540            props.min_width(),
1541            props.max_width(),
1542            constraints.min_width,
1543            constraints.max_width,
1544        );
1545        height = resolve_dimension(
1546            height,
1547            props.height(),
1548            props.min_height(),
1549            props.max_height(),
1550            constraints.min_height,
1551            constraints.max_height,
1552        );
1553
1554        let mut children = Vec::with_capacity(placements.len());
1555        let mut measured_children_by_id = measured_children.borrow_mut();
1556
1557        if let Ok(mut applier) = applier_host.try_borrow_typed() {
1558            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
1559                parent_node.set_measured_size(Size { width, height });
1560                parent_node.clear_needs_measure();
1561                parent_node.clear_needs_layout();
1562            });
1563        }
1564
1565        for placement in &placements {
1566            let child = if let Some(measured) = measured_children_by_id.remove(&placement.node_id) {
1567                measured
1568            } else {
1569                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?
1570            };
1571            let policy_position = Point {
1572                x: padding.left + placement.x,
1573                y: padding.top + placement.y,
1574            };
1575            let retained_position = Point {
1576                x: policy_position.x + child.offset.x,
1577                y: policy_position.y + child.offset.y,
1578            };
1579
1580            if let Ok(mut applier) = applier_host.try_borrow_typed()
1581                && applier
1582                    .with_node::<LayoutNode, _>(placement.node_id, |node| {
1583                        node.set_position(retained_position);
1584                    })
1585                    .is_err()
1586            {
1587                let _ = applier.with_node::<SubcomposeLayoutNode, _>(placement.node_id, |node| {
1588                    node.set_position(retained_position);
1589                });
1590            }
1591
1592            children.push(MeasuredChild {
1593                node: child,
1594                offset: policy_position,
1595            });
1596        }
1597
1598        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1599        node_handle.recycle_placement_scratch(placements);
1600
1601        Ok(Some(Rc::new(MeasuredNode::new(
1602            node_id,
1603            Size { width, height },
1604            offset,
1605            Point::default(),
1606            children,
1607        ))))
1608    }
1609    fn measure_through_modifier_chain(
1610        state_rc: &Rc<RefCell<Self>>,
1611        node_id: NodeId,
1612        runtime_state: &mut LayoutRuntimeState,
1613        measure_policy: &Rc<dyn MeasurePolicy>,
1614        constraints: Constraints,
1615        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1616        placements: &mut Vec<Placement>,
1617    ) -> ModifierChainMeasurement {
1618        use cranpose_foundation::NodeCapabilities;
1619
1620        layout_node_data.clear();
1621        let mut offset = Point::default();
1622        let mut density = crate::density::Density::default();
1623
1624        {
1625            let state = state_rc.borrow();
1626            let mut applier = state.applier.borrow_typed();
1627
1628            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1629                density = layout_node.density();
1630                let chain_handle = layout_node.modifier_chain();
1631
1632                if !chain_handle.has_layout_nodes() {
1633                    return;
1634                }
1635
1636                chain_handle.chain().for_each_forward_matching(
1637                    NodeCapabilities::LAYOUT,
1638                    |node_ref| {
1639                        if let Some(index) = node_ref.entry_index() {
1640                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1641                                layout_node_data.push((index, Rc::clone(&node_rc)));
1642                            }
1643
1644                            node_ref.with_node(|node| {
1645                                if let Some(offset_node) =
1646                                    node.as_any()
1647                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1648                                {
1649                                    let delta = offset_node.offset();
1650                                    offset.x += delta.x;
1651                                    offset.y += delta.y;
1652                                }
1653                            });
1654                        }
1655                    },
1656                );
1657            });
1658        }
1659
1660        let scope = crate::density::DensityMeasureScope::new(density);
1661
1662        if layout_node_data.is_empty() {
1663            let final_size = measure_policy.measure_into(
1664                &scope,
1665                runtime_state.child_measurables(),
1666                constraints,
1667                placements,
1668            );
1669
1670            return ModifierChainMeasurement {
1671                size: final_size,
1672                content_offset: Point::default(),
1673                offset,
1674            };
1675        }
1676
1677        runtime_state.reconcile_coordinator_chain(layout_node_data.as_slice());
1678        let frame = CoordinatorFrame::new(
1679            measure_policy,
1680            &scope,
1681            runtime_state.child_measurables(),
1682            placements,
1683        );
1684
1685        let placeable = runtime_state
1686            .coordinator_chain()
1687            .measure_from(0, &frame, constraints);
1688        let final_size = Size {
1689            width: placeable.width(),
1690            height: placeable.height(),
1691        };
1692
1693        let content_offset = placeable.content_offset();
1694        let all_placement_offset = Point {
1695            x: content_offset.0,
1696            y: content_offset.1,
1697        };
1698
1699        let content_offset = Point {
1700            x: all_placement_offset.x - offset.x,
1701            y: all_placement_offset.y - offset.y,
1702        };
1703
1704        let invalidations = frame.take_invalidations();
1705        if !invalidations.is_empty() {
1706            Self::with_applier_result(state_rc, |applier| {
1707                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1708                    for kind in invalidations {
1709                        match kind {
1710                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1711                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1712                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1713                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1714                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1715                        }
1716                    }
1717                })
1718            })
1719            .ok();
1720        }
1721
1722        ModifierChainMeasurement {
1723            size: final_size,
1724            content_offset,
1725            offset,
1726        }
1727    }
1728
1729    fn layout_child_measure_data(
1730        applier: &mut MemoryApplier,
1731        child_id: NodeId,
1732    ) -> Result<Option<LayoutChildMeasureData>, NodeError> {
1733        match applier.with_node::<LayoutNode, _>(child_id, |n| LayoutChildMeasureData {
1734            cache: n.cache_handles(),
1735            layout_state: Some(n.layout_state_handle()),
1736            needs_layout: n.needs_layout(),
1737            needs_measure: n.needs_measure(),
1738        }) {
1739            Ok(data) => Ok(Some(data)),
1740            Err(NodeError::TypeMismatch { .. }) => {
1741                match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |n| {
1742                    LayoutChildMeasureData {
1743                        cache: n.cache_handles(),
1744                        layout_state: None,
1745                        needs_layout: n.needs_layout(),
1746                        needs_measure: n.needs_measure(),
1747                    }
1748                }) {
1749                    Ok(data) => Ok(Some(data)),
1750                    Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
1751                        Ok(None)
1752                    }
1753                    Err(err) => Err(err),
1754                }
1755            }
1756            Err(NodeError::Missing { .. }) => Ok(None),
1757            Err(err) => Err(err),
1758        }
1759    }
1760
1761    fn measure_layout_node(
1762        state_rc: Rc<RefCell<Self>>,
1763        node_id: NodeId,
1764        snapshot: LayoutNodeSnapshot,
1765        constraints: Constraints,
1766    ) -> Result<Rc<MeasuredNode>, NodeError> {
1767        let cache_epoch = {
1768            let state = state_rc.borrow();
1769            state.cache_epoch
1770        };
1771        let LayoutNodeSnapshot {
1772            measure_policy,
1773            cache,
1774            layout_runtime_state,
1775            needs_layout,
1776            needs_measure,
1777        } = snapshot;
1778        cache.activate(cache_epoch);
1779
1780        if !needs_measure
1781            && !needs_layout
1782            && let Some(cached) = cache.get_measurement(constraints)
1783        {
1784            Self::with_applier_result(&state_rc, |applier| {
1785                applier.with_node::<LayoutNode, _>(node_id, |node| {
1786                    node.clear_needs_measure();
1787                    node.clear_needs_layout();
1788                })
1789            })
1790            .ok();
1791            return Ok(cached);
1792        }
1793
1794        let (runtime_handle, applier_host) = {
1795            let state = state_rc.borrow();
1796            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1797        };
1798
1799        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1800        let error = Rc::new(RefCell::new(None));
1801        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1802        let (records, child_ids, layout_node_data, placements) = pools.parts();
1803
1804        applier_host
1805            .borrow_typed()
1806            .with_node::<LayoutNode, _>(node_id, |node| {
1807                child_ids.extend_from_slice(&node.children);
1808            })?;
1809
1810        let mut valid_child_count = 0;
1811        for index in 0..child_ids.len() {
1812            let child_id = child_ids[index];
1813            let child_exists = {
1814                let mut applier = applier_host.borrow_typed();
1815                Self::layout_child_measure_data(&mut applier, child_id)?.is_some()
1816            };
1817            if child_exists {
1818                child_ids[valid_child_count] = child_id;
1819                valid_child_count += 1;
1820            }
1821        }
1822        child_ids.truncate(valid_child_count);
1823
1824        let _frame_binding_cleanup =
1825            LayoutRuntimeFrameBindingCleanup::new(Rc::clone(&layout_runtime_state));
1826
1827        {
1828            let mut runtime_state = layout_runtime_state.borrow_mut();
1829            runtime_state.reconcile_child_measurables(child_ids.as_slice());
1830
1831            for (index, &child_id) in child_ids.iter().enumerate() {
1832                let data = {
1833                    let mut applier = applier_host.borrow_typed();
1834                    Self::layout_child_measure_data(&mut applier, child_id)?
1835                };
1836                let Some(data) = data else {
1837                    continue;
1838                };
1839
1840                let child_is_dirty = data.needs_layout || data.needs_measure;
1841                let child_cache_epoch = if child_is_dirty {
1842                    cache_epoch
1843                } else {
1844                    data.cache.epoch()
1845                };
1846                let child_state = runtime_state.child_state(index);
1847                child_state.configure(LayoutChildMeasureConfig {
1848                    applier: Rc::clone(&applier_host),
1849                    node_id: child_id,
1850                    error: Rc::clone(&error),
1851                    runtime_handle: runtime_handle.clone(),
1852                    cache: data.cache,
1853                    cache_epoch: child_cache_epoch,
1854                    force_remeasure: child_is_dirty,
1855                    measure_handle: Some(measure_handle.clone()),
1856                    layout_state: data.layout_state,
1857                });
1858                records.push((child_id, ChildRecord { state: child_state }));
1859            }
1860        }
1861
1862        let chain_constraints = constraints;
1863
1864        let modifier_chain_result = {
1865            let mut runtime_state = layout_runtime_state.borrow_mut();
1866            Self::measure_through_modifier_chain(
1867                &state_rc,
1868                node_id,
1869                &mut runtime_state,
1870                &measure_policy,
1871                chain_constraints,
1872                layout_node_data,
1873                placements,
1874            )
1875        };
1876
1877        let (width, height, content_offset, offset) = {
1878            let result = modifier_chain_result;
1879            if let Some(err) = error.borrow_mut().take() {
1880                return Err(err);
1881            }
1882
1883            (
1884                result.size.width,
1885                result.size.height,
1886                result.content_offset,
1887                result.offset,
1888            )
1889        };
1890
1891        let mut measured_children = Vec::with_capacity(records.len());
1892        for (child_id, record) in records.iter() {
1893            if let Some(measured) = record.state.take_measured() {
1894                let placed = placements
1895                    .iter()
1896                    .find(|placement| placement.node_id == *child_id)
1897                    .map(|placement| Point {
1898                        x: placement.x,
1899                        y: placement.y,
1900                    });
1901                if let Some(raw) = placed {
1902                    record.state.place_retained(Point {
1903                        x: raw.x + measured.offset.x,
1904                        y: raw.y + measured.offset.y,
1905                    });
1906                }
1907                let base_position = placed
1908                    .or_else(|| record.state.last_position())
1909                    .unwrap_or(Point { x: 0.0, y: 0.0 });
1910                let position = Point {
1911                    x: content_offset.x + base_position.x,
1912                    y: content_offset.y + base_position.y,
1913                };
1914                measured_children.push(MeasuredChild {
1915                    node: measured,
1916                    offset: position,
1917                });
1918            }
1919        }
1920
1921        let measured = Rc::new(MeasuredNode::new(
1922            node_id,
1923            Size { width, height },
1924            offset,
1925            content_offset,
1926            measured_children,
1927        ));
1928
1929        cache.store_measurement(constraints, Rc::clone(&measured));
1930
1931        Self::with_applier_result(&state_rc, |applier| {
1932            applier.with_node::<LayoutNode, _>(node_id, |node| {
1933                node.clear_needs_measure();
1934                node.clear_needs_layout();
1935                node.set_measured_size(Size { width, height });
1936                node.set_content_offset(content_offset);
1937            })
1938        })
1939        .ok();
1940
1941        Ok(measured)
1942    }
1943}
1944
1945struct LayoutChildMeasureData {
1946    cache: LayoutNodeCacheHandles,
1947    layout_state: Option<Rc<RefCell<LayoutState>>>,
1948    needs_layout: bool,
1949    needs_measure: bool,
1950}
1951
1952struct LayoutNodeSnapshot {
1953    measure_policy: Rc<dyn MeasurePolicy>,
1954    cache: LayoutNodeCacheHandles,
1955    layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
1956    needs_layout: bool,
1957    needs_measure: bool,
1958}
1959
1960impl LayoutNodeSnapshot {
1961    fn from_layout_node(node: &LayoutNode) -> Self {
1962        Self {
1963            measure_policy: Rc::clone(&node.measure_policy),
1964            cache: node.cache_handles(),
1965            layout_runtime_state: node.layout_runtime_state_handle(),
1966            needs_layout: node.needs_layout(),
1967            needs_measure: node.needs_measure(),
1968        }
1969    }
1970}
1971
1972struct VecPools {
1973    state: Rc<RefCell<LayoutBuilderState>>,
1974    records: Vec<(NodeId, ChildRecord)>,
1975    child_ids: Vec<NodeId>,
1976    layout_node_data: Vec<LayoutModifierNodeData>,
1977    placements: Vec<Placement>,
1978}
1979
1980impl VecPools {
1981    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1982        let (records, child_ids, layout_node_data, placements) = {
1983            let mut state_mut = state.borrow_mut();
1984            (
1985                state_mut.frame_arena.tmp_records.acquire(),
1986                state_mut.frame_arena.tmp_child_ids.acquire(),
1987                state_mut.frame_arena.tmp_layout_node_data.acquire(),
1988                state_mut.frame_arena.tmp_placements.acquire(),
1989            )
1990        };
1991        Self {
1992            state,
1993            records,
1994            child_ids,
1995            layout_node_data,
1996            placements,
1997        }
1998    }
1999
2000    #[allow(clippy::type_complexity)]
2001    fn parts(
2002        &mut self,
2003    ) -> (
2004        &mut Vec<(NodeId, ChildRecord)>,
2005        &mut Vec<NodeId>,
2006        &mut Vec<LayoutModifierNodeData>,
2007        &mut Vec<Placement>,
2008    ) {
2009        (
2010            &mut self.records,
2011            &mut self.child_ids,
2012            &mut self.layout_node_data,
2013            &mut self.placements,
2014        )
2015    }
2016}
2017
2018impl Drop for VecPools {
2019    fn drop(&mut self) {
2020        let mut state = self.state.borrow_mut();
2021        state
2022            .frame_arena
2023            .tmp_records
2024            .release(std::mem::take(&mut self.records));
2025        state
2026            .frame_arena
2027            .tmp_child_ids
2028            .release(std::mem::take(&mut self.child_ids));
2029        state
2030            .frame_arena
2031            .tmp_layout_node_data
2032            .release(std::mem::take(&mut self.layout_node_data));
2033        state
2034            .frame_arena
2035            .tmp_placements
2036            .release(std::mem::take(&mut self.placements));
2037    }
2038}
2039
2040struct SlotsGuard {
2041    state: Rc<RefCell<LayoutBuilderState>>,
2042    slots: Option<SlotTable>,
2043}
2044
2045impl SlotsGuard {
2046    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2047        let slots = {
2048            let state_ref = state.borrow();
2049            let mut slots_ref = state_ref.slots.borrow_mut();
2050            std::mem::take(&mut *slots_ref)
2051        };
2052        Self {
2053            state,
2054            slots: Some(slots),
2055        }
2056    }
2057
2058    fn host(&mut self) -> Rc<SlotsHost> {
2059        let slots = self.slots.take().unwrap_or_default();
2060        Rc::new(SlotsHost::new(slots))
2061    }
2062
2063    fn restore(&mut self, slots: SlotTable) {
2064        debug_assert!(self.slots.is_none());
2065        self.slots = Some(slots);
2066    }
2067}
2068
2069impl Drop for SlotsGuard {
2070    fn drop(&mut self) {
2071        if let Some(slots) = self.slots.take() {
2072            let state_ref = self.state.borrow();
2073            *state_ref.slots.borrow_mut() = slots;
2074        }
2075    }
2076}
2077
2078#[derive(Clone)]
2079struct LayoutMeasureHandle {
2080    state: Rc<RefCell<LayoutBuilderState>>,
2081}
2082
2083impl LayoutMeasureHandle {
2084    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2085        Self { state }
2086    }
2087
2088    fn measure(
2089        &self,
2090        node_id: NodeId,
2091        constraints: Constraints,
2092    ) -> Result<Rc<MeasuredNode>, NodeError> {
2093        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
2094    }
2095}
2096
2097#[derive(Debug, Clone)]
2098pub(crate) struct MeasuredNode {
2099    node_id: NodeId,
2100    size: Size,
2101    offset: Point,
2102    content_offset: Point,
2103    children: Vec<MeasuredChild>,
2104}
2105
2106impl MeasuredNode {
2107    fn new(
2108        node_id: NodeId,
2109        size: Size,
2110        offset: Point,
2111        content_offset: Point,
2112        children: Vec<MeasuredChild>,
2113    ) -> Self {
2114        Self {
2115            node_id,
2116            size,
2117            offset,
2118            content_offset,
2119            children,
2120        }
2121    }
2122
2123    #[cfg(test)]
2124    pub(crate) fn leaf(node_id: NodeId, size: Size) -> Self {
2125        Self::new(
2126            node_id,
2127            size,
2128            Point::default(),
2129            Point::default(),
2130            Vec::new(),
2131        )
2132    }
2133
2134    pub(crate) fn node_id(&self) -> NodeId {
2135        self.node_id
2136    }
2137
2138    pub(crate) fn size(&self) -> Size {
2139        self.size
2140    }
2141}
2142
2143#[derive(Debug, Clone)]
2144struct MeasuredChild {
2145    node: Rc<MeasuredNode>,
2146    offset: Point,
2147}
2148
2149struct ChildRecord {
2150    state: Rc<LayoutChildMeasureState>,
2151}
2152
2153struct CoordinatorFrame<'a> {
2154    measure_policy: &'a Rc<dyn MeasurePolicy>,
2155    scope: &'a dyn cranpose_ui_layout::MeasureScope,
2156    measurables: &'a [Box<dyn Measurable>],
2157    placements: RefCell<&'a mut Vec<Placement>>,
2158    context: RefCell<LayoutNodeContext>,
2159}
2160
2161impl<'a> CoordinatorFrame<'a> {
2162    fn new(
2163        measure_policy: &'a Rc<dyn MeasurePolicy>,
2164        scope: &'a dyn cranpose_ui_layout::MeasureScope,
2165        measurables: &'a [Box<dyn Measurable>],
2166        placements: &'a mut Vec<Placement>,
2167    ) -> Self {
2168        Self {
2169            measure_policy,
2170            scope,
2171            measurables,
2172            placements: RefCell::new(placements),
2173            context: RefCell::new(LayoutNodeContext::new()),
2174        }
2175    }
2176
2177    fn take_invalidations(&self) -> Vec<InvalidationKind> {
2178        self.context.borrow_mut().take_invalidations()
2179    }
2180}
2181
2182struct CoordinatorLink<'chain, 'frame_ref, 'frame_data> {
2183    chain: &'chain CoordinatorChain,
2184    frame: &'frame_ref CoordinatorFrame<'frame_data>,
2185    index: usize,
2186}
2187
2188impl Measurable for CoordinatorLink<'_, '_, '_> {
2189    fn measure(&self, constraints: Constraints) -> Placeable {
2190        self.chain.measure_from(self.index, self.frame, constraints)
2191    }
2192
2193    fn min_intrinsic_width(&self, height: f32) -> f32 {
2194        self.chain
2195            .min_intrinsic_width_from(self.index, self.frame, height)
2196    }
2197
2198    fn max_intrinsic_width(&self, height: f32) -> f32 {
2199        self.chain
2200            .max_intrinsic_width_from(self.index, self.frame, height)
2201    }
2202
2203    fn min_intrinsic_height(&self, width: f32) -> f32 {
2204        self.chain
2205            .min_intrinsic_height_from(self.index, self.frame, width)
2206    }
2207
2208    fn max_intrinsic_height(&self, width: f32) -> f32 {
2209        self.chain
2210            .max_intrinsic_height_from(self.index, self.frame, width)
2211    }
2212}
2213
2214struct CoordinatorNode {
2215    modifier_index: usize,
2216    node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2217    measured_size: Cell<Size>,
2218    accumulated_offset: Cell<Point>,
2219}
2220
2221impl CoordinatorNode {
2222    fn new(
2223        modifier_index: usize,
2224        node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2225    ) -> Self {
2226        Self {
2227            modifier_index,
2228            node,
2229            measured_size: Cell::new(Size::default()),
2230            accumulated_offset: Cell::new(Point::default()),
2231        }
2232    }
2233
2234    fn matches(
2235        &self,
2236        modifier_index: usize,
2237        node: &Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2238    ) -> bool {
2239        self.modifier_index == modifier_index && Rc::ptr_eq(&self.node, node)
2240    }
2241
2242    #[cfg(test)]
2243    fn ptr(&self) -> usize {
2244        Rc::as_ptr(&self.node) as *const () as usize
2245    }
2246}
2247
2248#[derive(Default)]
2249struct CoordinatorChain {
2250    nodes: Vec<CoordinatorNode>,
2251}
2252
2253impl CoordinatorChain {
2254    fn reconcile(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2255        if self.matches(layout_node_data) {
2256            return;
2257        }
2258
2259        let mut previous_nodes = std::mem::take(&mut self.nodes);
2260        self.nodes.reserve(layout_node_data.len());
2261
2262        for (modifier_index, node) in layout_node_data.iter() {
2263            if let Some(position) = previous_nodes
2264                .iter()
2265                .position(|candidate| candidate.matches(*modifier_index, node))
2266            {
2267                self.nodes.push(previous_nodes.swap_remove(position));
2268            } else {
2269                self.nodes
2270                    .push(CoordinatorNode::new(*modifier_index, Rc::clone(node)));
2271            }
2272        }
2273    }
2274
2275    fn matches(&self, layout_node_data: &[LayoutModifierNodeData]) -> bool {
2276        self.nodes.len() == layout_node_data.len()
2277            && self
2278                .nodes
2279                .iter()
2280                .zip(layout_node_data.iter())
2281                .all(|(node, (modifier_index, node_rc))| node.matches(*modifier_index, node_rc))
2282    }
2283
2284    fn measure_from(
2285        &self,
2286        index: usize,
2287        frame: &CoordinatorFrame<'_>,
2288        constraints: Constraints,
2289    ) -> Placeable {
2290        let Some(node) = self.nodes.get(index) else {
2291            let mut placements = frame.placements.borrow_mut();
2292            let size = frame.measure_policy.measure_into(
2293                frame.scope,
2294                frame.measurables,
2295                constraints,
2296                &mut placements,
2297            );
2298            return Placeable::value(size.width, size.height, NodeId::default());
2299        };
2300
2301        let wrapped = CoordinatorLink {
2302            chain: self,
2303            frame,
2304            index: index + 1,
2305        };
2306        let node_borrow = node.node.borrow();
2307
2308        let Some(layout_node) = node_borrow.as_layout_node() else {
2309            let placeable = wrapped.measure(constraints);
2310            let child_accumulated = self.total_content_offset_from(index + 1);
2311            node.accumulated_offset.set(child_accumulated);
2312            return Placeable::value_with_offset(
2313                placeable.width(),
2314                placeable.height(),
2315                NodeId::default(),
2316                (child_accumulated.x, child_accumulated.y),
2317            );
2318        };
2319
2320        let result = match frame.context.try_borrow_mut() {
2321            Ok(mut context) => layout_node.measure(&mut *context, &wrapped, constraints),
2322            Err(_) => {
2323                let mut temp = LayoutNodeContext::new();
2324                let result = layout_node.measure(&mut temp, &wrapped, constraints);
2325                if let Ok(mut context) = frame.context.try_borrow_mut() {
2326                    for kind in temp.take_invalidations() {
2327                        context.invalidate(kind);
2328                    }
2329                }
2330                result
2331            }
2332        };
2333
2334        node.measured_size.set(result.size);
2335        let local_offset = Point {
2336            x: result.placement_offset_x,
2337            y: result.placement_offset_y,
2338        };
2339        let child_accumulated = self.total_content_offset_from(index + 1);
2340        let accumulated = Point {
2341            x: local_offset.x + child_accumulated.x,
2342            y: local_offset.y + child_accumulated.y,
2343        };
2344        node.accumulated_offset.set(accumulated);
2345
2346        Placeable::value_with_offset(
2347            result.size.width,
2348            result.size.height,
2349            NodeId::default(),
2350            (accumulated.x, accumulated.y),
2351        )
2352    }
2353
2354    fn min_intrinsic_width_from(
2355        &self,
2356        index: usize,
2357        frame: &CoordinatorFrame<'_>,
2358        height: f32,
2359    ) -> f32 {
2360        let Some(node) = self.nodes.get(index) else {
2361            return frame
2362                .measure_policy
2363                .min_intrinsic_width(frame.measurables, height);
2364        };
2365        let wrapped = CoordinatorLink {
2366            chain: self,
2367            frame,
2368            index: index + 1,
2369        };
2370        let node_borrow = node.node.borrow();
2371        node_borrow
2372            .as_layout_node()
2373            .map(|layout_node| layout_node.min_intrinsic_width(&wrapped, height))
2374            .unwrap_or_else(|| wrapped.min_intrinsic_width(height))
2375    }
2376
2377    fn max_intrinsic_width_from(
2378        &self,
2379        index: usize,
2380        frame: &CoordinatorFrame<'_>,
2381        height: f32,
2382    ) -> f32 {
2383        let Some(node) = self.nodes.get(index) else {
2384            return frame
2385                .measure_policy
2386                .max_intrinsic_width(frame.measurables, height);
2387        };
2388        let wrapped = CoordinatorLink {
2389            chain: self,
2390            frame,
2391            index: index + 1,
2392        };
2393        let node_borrow = node.node.borrow();
2394        node_borrow
2395            .as_layout_node()
2396            .map(|layout_node| layout_node.max_intrinsic_width(&wrapped, height))
2397            .unwrap_or_else(|| wrapped.max_intrinsic_width(height))
2398    }
2399
2400    fn min_intrinsic_height_from(
2401        &self,
2402        index: usize,
2403        frame: &CoordinatorFrame<'_>,
2404        width: f32,
2405    ) -> f32 {
2406        let Some(node) = self.nodes.get(index) else {
2407            return frame
2408                .measure_policy
2409                .min_intrinsic_height(frame.measurables, width);
2410        };
2411        let wrapped = CoordinatorLink {
2412            chain: self,
2413            frame,
2414            index: index + 1,
2415        };
2416        let node_borrow = node.node.borrow();
2417        node_borrow
2418            .as_layout_node()
2419            .map(|layout_node| layout_node.min_intrinsic_height(&wrapped, width))
2420            .unwrap_or_else(|| wrapped.min_intrinsic_height(width))
2421    }
2422
2423    fn max_intrinsic_height_from(
2424        &self,
2425        index: usize,
2426        frame: &CoordinatorFrame<'_>,
2427        width: f32,
2428    ) -> f32 {
2429        let Some(node) = self.nodes.get(index) else {
2430            return frame
2431                .measure_policy
2432                .max_intrinsic_height(frame.measurables, width);
2433        };
2434        let wrapped = CoordinatorLink {
2435            chain: self,
2436            frame,
2437            index: index + 1,
2438        };
2439        let node_borrow = node.node.borrow();
2440        node_borrow
2441            .as_layout_node()
2442            .map(|layout_node| layout_node.max_intrinsic_height(&wrapped, width))
2443            .unwrap_or_else(|| wrapped.max_intrinsic_height(width))
2444    }
2445
2446    fn total_content_offset_from(&self, index: usize) -> Point {
2447        self.nodes
2448            .get(index)
2449            .map(|node| node.accumulated_offset.get())
2450            .unwrap_or_default()
2451    }
2452
2453    #[cfg(test)]
2454    fn debug_ptrs(&self) -> Vec<usize> {
2455        self.nodes.iter().map(CoordinatorNode::ptr).collect()
2456    }
2457}
2458
2459#[derive(Default)]
2460pub(crate) struct LayoutRuntimeState {
2461    child_ids: Vec<NodeId>,
2462    child_states: Vec<Rc<LayoutChildMeasureState>>,
2463    child_measurables: Vec<Box<dyn Measurable>>,
2464    coordinator_chain: CoordinatorChain,
2465}
2466
2467impl LayoutRuntimeState {
2468    fn reconcile_child_measurables(&mut self, child_ids: &[NodeId]) {
2469        if self.child_ids == child_ids {
2470            return;
2471        }
2472
2473        let mut previous_ids = std::mem::take(&mut self.child_ids);
2474        let mut previous_states = std::mem::take(&mut self.child_states);
2475        let mut previous_measurables = std::mem::take(&mut self.child_measurables);
2476
2477        self.child_ids.reserve(child_ids.len());
2478        self.child_states.reserve(child_ids.len());
2479        self.child_measurables.reserve(child_ids.len());
2480
2481        for &child_id in child_ids {
2482            if let Some(position) = previous_ids.iter().position(|&id| id == child_id) {
2483                self.child_ids.push(previous_ids.swap_remove(position));
2484                self.child_states
2485                    .push(previous_states.swap_remove(position));
2486                self.child_measurables
2487                    .push(previous_measurables.swap_remove(position));
2488            } else {
2489                let state = LayoutChildMeasureState::new(child_id);
2490                self.child_ids.push(child_id);
2491                self.child_states.push(Rc::clone(&state));
2492                self.child_measurables
2493                    .push(Box::new(LayoutChildMeasurable::new(state)));
2494            }
2495        }
2496    }
2497
2498    fn child_state(&self, index: usize) -> Rc<LayoutChildMeasureState> {
2499        Rc::clone(&self.child_states[index])
2500    }
2501
2502    fn child_measurables(&self) -> &[Box<dyn Measurable>] {
2503        self.child_measurables.as_slice()
2504    }
2505
2506    fn reconcile_coordinator_chain(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2507        self.coordinator_chain.reconcile(layout_node_data);
2508    }
2509
2510    fn coordinator_chain(&self) -> &CoordinatorChain {
2511        &self.coordinator_chain
2512    }
2513
2514    fn clear_frame_bindings(&self) {
2515        for child_state in &self.child_states {
2516            child_state.clear_frame_bindings();
2517        }
2518    }
2519
2520    #[cfg(test)]
2521    pub(crate) fn debug_stats(&self) -> LayoutRuntimeDebugStats {
2522        LayoutRuntimeDebugStats {
2523            child_ids: self.child_ids.clone(),
2524            child_state_ptrs: self
2525                .child_states
2526                .iter()
2527                .map(|state| Rc::as_ptr(state) as *const () as usize)
2528                .collect(),
2529            child_measurable_ptrs: self
2530                .child_measurables
2531                .iter()
2532                .map(|measurable| {
2533                    measurable.as_ref() as *const dyn Measurable as *const () as usize
2534                })
2535                .collect(),
2536            child_measurable_count: self.child_measurables.len(),
2537            coordinator_node_ptrs: self.coordinator_chain.debug_ptrs(),
2538            coordinator_node_count: self.coordinator_chain.nodes.len(),
2539        }
2540    }
2541}
2542
2543#[cfg(test)]
2544#[derive(Debug, Clone, PartialEq, Eq)]
2545pub(crate) struct LayoutRuntimeDebugStats {
2546    pub(crate) child_ids: Vec<NodeId>,
2547    pub(crate) child_state_ptrs: Vec<usize>,
2548    pub(crate) child_measurable_ptrs: Vec<usize>,
2549    pub(crate) child_measurable_count: usize,
2550    pub(crate) coordinator_node_ptrs: Vec<usize>,
2551    pub(crate) coordinator_node_count: usize,
2552}
2553
2554struct LayoutChildMeasureConfig {
2555    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2556    node_id: NodeId,
2557    error: Rc<RefCell<Option<NodeError>>>,
2558    runtime_handle: Option<RuntimeHandle>,
2559    cache: LayoutNodeCacheHandles,
2560    cache_epoch: u64,
2561    force_remeasure: bool,
2562    measure_handle: Option<LayoutMeasureHandle>,
2563    layout_state: Option<Rc<RefCell<LayoutState>>>,
2564}
2565
2566struct LayoutChildMeasureState {
2567    applier: RefCell<Option<Rc<ConcreteApplierHost<MemoryApplier>>>>,
2568    node_id: Cell<NodeId>,
2569    measured: RefCell<Option<Rc<MeasuredNode>>>,
2570    last_position: Cell<Option<Point>>,
2571    error: RefCell<Option<Rc<RefCell<Option<NodeError>>>>>,
2572    runtime_handle: RefCell<Option<RuntimeHandle>>,
2573    cache: RefCell<LayoutNodeCacheHandles>,
2574    cache_epoch: Cell<u64>,
2575    force_remeasure: Cell<bool>,
2576    measure_handle: RefCell<Option<LayoutMeasureHandle>>,
2577    layout_state: RefCell<Option<Rc<RefCell<LayoutState>>>>,
2578}
2579
2580impl LayoutChildMeasureState {
2581    fn new(node_id: NodeId) -> Rc<Self> {
2582        Rc::new(Self {
2583            applier: RefCell::new(None),
2584            node_id: Cell::new(node_id),
2585            measured: RefCell::new(None),
2586            last_position: Cell::new(None),
2587            error: RefCell::new(None),
2588            runtime_handle: RefCell::new(None),
2589            cache: RefCell::new(LayoutNodeCacheHandles::default()),
2590            cache_epoch: Cell::new(0),
2591            force_remeasure: Cell::new(true),
2592            measure_handle: RefCell::new(None),
2593            layout_state: RefCell::new(None),
2594        })
2595    }
2596
2597    fn configure(&self, config: LayoutChildMeasureConfig) {
2598        config.cache.activate(config.cache_epoch);
2599        *self.applier.borrow_mut() = Some(config.applier);
2600        self.node_id.set(config.node_id);
2601        self.measured.borrow_mut().take();
2602        self.last_position.set(None);
2603        *self.error.borrow_mut() = Some(config.error);
2604        *self.runtime_handle.borrow_mut() = config.runtime_handle;
2605        *self.cache.borrow_mut() = config.cache;
2606        self.cache_epoch.set(config.cache_epoch);
2607        self.force_remeasure.set(config.force_remeasure);
2608        *self.measure_handle.borrow_mut() = config.measure_handle;
2609        *self.layout_state.borrow_mut() = config.layout_state;
2610    }
2611
2612    fn clear_frame_bindings(&self) {
2613        self.measured.borrow_mut().take();
2614        *self.applier.borrow_mut() = None;
2615        *self.error.borrow_mut() = None;
2616        *self.runtime_handle.borrow_mut() = None;
2617        *self.measure_handle.borrow_mut() = None;
2618        *self.layout_state.borrow_mut() = None;
2619    }
2620
2621    fn node_id(&self) -> NodeId {
2622        self.node_id.get()
2623    }
2624
2625    fn cache(&self) -> LayoutNodeCacheHandles {
2626        self.cache.borrow().clone()
2627    }
2628
2629    fn applier(&self) -> Option<Rc<ConcreteApplierHost<MemoryApplier>>> {
2630        self.applier.borrow().clone()
2631    }
2632
2633    fn layout_state(&self) -> Option<Rc<RefCell<LayoutState>>> {
2634        self.layout_state.borrow().clone()
2635    }
2636
2637    fn take_measured(&self) -> Option<Rc<MeasuredNode>> {
2638        self.measured.borrow_mut().take()
2639    }
2640
2641    fn last_position(&self) -> Option<Point> {
2642        self.last_position.get()
2643    }
2644
2645    fn set_last_position(&self, position: Point) {
2646        self.last_position.set(Some(position));
2647    }
2648
2649    fn place_retained(&self, position: Point) {
2650        self.set_last_position(position);
2651        if let Some(layout_state) = self.layout_state() {
2652            layout_state.borrow_mut().place(position);
2653            return;
2654        }
2655        let Some(applier) = self.applier() else {
2656            return;
2657        };
2658        let Ok(mut applier) = applier.try_borrow_typed() else {
2659            return;
2660        };
2661        let node_id = self.node_id();
2662        if applier
2663            .with_node::<LayoutNode, _>(node_id, |node| {
2664                node.set_position(position);
2665            })
2666            .is_err()
2667        {
2668            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2669                node.set_position(position);
2670            });
2671        }
2672    }
2673
2674    fn set_measured(&self, measured: Option<Rc<MeasuredNode>>) {
2675        *self.measured.borrow_mut() = measured;
2676    }
2677
2678    fn record_error(&self, err: NodeError) {
2679        let Some(error) = self.error.borrow().clone() else {
2680            return;
2681        };
2682        let mut slot = error.borrow_mut();
2683        if slot.is_none() {
2684            *slot = Some(err);
2685        }
2686    }
2687
2688    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
2689        let node_id = self.node_id();
2690        if let Some(handle) = self.measure_handle.borrow().clone() {
2691            return handle.measure(node_id, constraints);
2692        }
2693        let applier = self.applier().ok_or(NodeError::MissingContext {
2694            id: node_id,
2695            reason: "layout child applier not configured",
2696        })?;
2697        measure_node_with_host(
2698            applier,
2699            self.runtime_handle.borrow().clone(),
2700            node_id,
2701            constraints,
2702            self.cache_epoch.get(),
2703        )
2704    }
2705
2706    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
2707        let cache = self.cache();
2708        cache.activate(self.cache_epoch.get());
2709        if !self.force_remeasure.get()
2710            && let Some(cached) = cache.get_measurement(constraints)
2711        {
2712            return Some(cached);
2713        }
2714
2715        match self.perform_measure(constraints) {
2716            Ok(measured) => {
2717                self.force_remeasure.set(false);
2718                cache.store_measurement(constraints, Rc::clone(&measured));
2719                Some(measured)
2720            }
2721            Err(err) => {
2722                self.record_error(err);
2723                None
2724            }
2725        }
2726    }
2727}
2728
2729struct LayoutChildMeasurable {
2730    state: Rc<LayoutChildMeasureState>,
2731}
2732
2733impl LayoutChildMeasurable {
2734    fn new(state: Rc<LayoutChildMeasureState>) -> Self {
2735        Self { state }
2736    }
2737
2738    fn resolved_parent_data(&self) -> Option<cranpose_ui_layout::ParentData> {
2739        let applier = self.state.applier()?;
2740        let node_id = self.state.node_id();
2741        let Ok(mut applier) = applier.try_borrow_typed() else {
2742            return None;
2743        };
2744
2745        applier
2746            .with_node::<LayoutNode, _>(node_id, |layout_node| {
2747                let props = layout_node.resolved_modifiers().layout_properties();
2748                let weight = props.weight().unwrap_or_default();
2749                cranpose_ui_layout::ParentData {
2750                    weight: weight.weight,
2751                    fill: weight.fill,
2752                    box_alignment: props.box_alignment(),
2753                    row_alignment: props.row_alignment(),
2754                    column_alignment: props.column_alignment(),
2755                }
2756            })
2757            .ok()
2758    }
2759}
2760
2761impl Measurable for LayoutChildMeasurable {
2762    fn measure(&self, constraints: Constraints) -> Placeable {
2763        let state = &self.state;
2764        let cache = state.cache();
2765        cache.activate(state.cache_epoch.get());
2766        let measured_size;
2767        if !state.force_remeasure.get() {
2768            if let Some(cached) = cache.get_measurement(constraints) {
2769                measured_size = cached.size;
2770                state.set_measured(Some(Rc::clone(&cached)));
2771            } else {
2772                match state.perform_measure(constraints) {
2773                    Ok(measured) => {
2774                        state.force_remeasure.set(false);
2775                        measured_size = measured.size;
2776                        cache.store_measurement(constraints, Rc::clone(&measured));
2777                        state.set_measured(Some(measured));
2778                    }
2779                    Err(err) => {
2780                        state.record_error(err);
2781                        state.set_measured(None);
2782                        measured_size = Size {
2783                            width: 0.0,
2784                            height: 0.0,
2785                        };
2786                    }
2787                }
2788            }
2789        } else {
2790            match state.perform_measure(constraints) {
2791                Ok(measured) => {
2792                    state.force_remeasure.set(false);
2793                    measured_size = measured.size;
2794                    cache.store_measurement(constraints, Rc::clone(&measured));
2795                    state.set_measured(Some(measured));
2796                }
2797                Err(err) => {
2798                    state.record_error(err);
2799                    state.set_measured(None);
2800                    measured_size = Size {
2801                        width: 0.0,
2802                        height: 0.0,
2803                    };
2804                }
2805            }
2806        }
2807
2808        if let Some(layout_state) = state.layout_state() {
2809            let mut layout_state = layout_state.borrow_mut();
2810            layout_state.set_size(measured_size);
2811            layout_state.measurement_constraints = constraints;
2812        } else if let Some(applier) = state.applier() {
2813            let Ok(mut applier) = applier.try_borrow_typed() else {
2814                return Placeable::value(
2815                    measured_size.width,
2816                    measured_size.height,
2817                    state.node_id(),
2818                );
2819            };
2820            let _ = applier.with_node::<LayoutNode, _>(state.node_id(), |node| {
2821                node.set_measured_size(measured_size);
2822                node.set_measurement_constraints(constraints);
2823            });
2824        }
2825
2826        let state = Rc::clone(&self.state);
2827        let node_id = state.node_id();
2828
2829        let place_fn = Rc::new(move |x: f32, y: f32| {
2830            let internal_offset = state
2831                .measured
2832                .borrow()
2833                .as_ref()
2834                .map(|m| m.offset)
2835                .unwrap_or_default();
2836
2837            state.place_retained(Point {
2838                x: x + internal_offset.x,
2839                y: y + internal_offset.y,
2840            });
2841        });
2842
2843        Placeable::with_place_fn(measured_size.width, measured_size.height, node_id, place_fn)
2844    }
2845
2846    fn min_intrinsic_width(&self, height: f32) -> f32 {
2847        let kind = IntrinsicKind::MinWidth(height);
2848        let cache = self.state.cache();
2849        cache.activate(self.state.cache_epoch.get());
2850        if !self.state.force_remeasure.get()
2851            && let Some(value) = cache.get_intrinsic(&kind)
2852        {
2853            return value;
2854        }
2855        let constraints = Constraints {
2856            min_width: 0.0,
2857            max_width: f32::INFINITY,
2858            min_height: height,
2859            max_height: height,
2860        };
2861        if let Some(node) = self.state.intrinsic_measure(constraints) {
2862            let value = node.size.width;
2863            cache.store_intrinsic(kind, value);
2864            value
2865        } else {
2866            0.0
2867        }
2868    }
2869
2870    fn max_intrinsic_width(&self, height: f32) -> f32 {
2871        let kind = IntrinsicKind::MaxWidth(height);
2872        let cache = self.state.cache();
2873        cache.activate(self.state.cache_epoch.get());
2874        if !self.state.force_remeasure.get()
2875            && let Some(value) = cache.get_intrinsic(&kind)
2876        {
2877            return value;
2878        }
2879        let constraints = Constraints {
2880            min_width: 0.0,
2881            max_width: f32::INFINITY,
2882            min_height: 0.0,
2883            max_height: height,
2884        };
2885        if let Some(node) = self.state.intrinsic_measure(constraints) {
2886            let value = node.size.width;
2887            cache.store_intrinsic(kind, value);
2888            value
2889        } else {
2890            0.0
2891        }
2892    }
2893
2894    fn min_intrinsic_height(&self, width: f32) -> f32 {
2895        let kind = IntrinsicKind::MinHeight(width);
2896        let cache = self.state.cache();
2897        cache.activate(self.state.cache_epoch.get());
2898        if !self.state.force_remeasure.get()
2899            && let Some(value) = cache.get_intrinsic(&kind)
2900        {
2901            return value;
2902        }
2903        let constraints = Constraints {
2904            min_width: width,
2905            max_width: width,
2906            min_height: 0.0,
2907            max_height: f32::INFINITY,
2908        };
2909        if let Some(node) = self.state.intrinsic_measure(constraints) {
2910            let value = node.size.height;
2911            cache.store_intrinsic(kind, value);
2912            value
2913        } else {
2914            0.0
2915        }
2916    }
2917
2918    fn max_intrinsic_height(&self, width: f32) -> f32 {
2919        let kind = IntrinsicKind::MaxHeight(width);
2920        let cache = self.state.cache();
2921        cache.activate(self.state.cache_epoch.get());
2922        if !self.state.force_remeasure.get()
2923            && let Some(value) = cache.get_intrinsic(&kind)
2924        {
2925            return value;
2926        }
2927        let constraints = Constraints {
2928            min_width: 0.0,
2929            max_width: width,
2930            min_height: 0.0,
2931            max_height: f32::INFINITY,
2932        };
2933        if let Some(node) = self.state.intrinsic_measure(constraints) {
2934            let value = node.size.height;
2935            cache.store_intrinsic(kind, value);
2936            value
2937        } else {
2938            0.0
2939        }
2940    }
2941
2942    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
2943        let parent_data = self.resolved_parent_data()?;
2944        if !parent_data.has_weight() {
2945            return None;
2946        }
2947        Some(cranpose_ui_layout::FlexParentData::new(
2948            parent_data.weight,
2949            parent_data.fill,
2950        ))
2951    }
2952
2953    fn parent_data(&self) -> cranpose_ui_layout::ParentData {
2954        self.resolved_parent_data().unwrap_or_default()
2955    }
2956}
2957
2958fn measure_node_with_host(
2959    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2960    runtime_handle: Option<RuntimeHandle>,
2961    node_id: NodeId,
2962    constraints: Constraints,
2963    epoch: u64,
2964) -> Result<Rc<MeasuredNode>, NodeError> {
2965    let runtime_handle = match runtime_handle {
2966        Some(handle) => Some(handle),
2967        None => applier.borrow_typed().runtime_handle(),
2968    };
2969    let mut builder = LayoutBuilder::new_with_epoch(
2970        applier,
2971        epoch,
2972        Rc::new(RefCell::new(SlotTable::default())),
2973        FrameLayoutArena::default(),
2974    );
2975    builder.set_runtime_handle(runtime_handle);
2976    builder.measure_node(node_id, constraints)
2977}
2978
2979#[derive(Clone)]
2980struct RuntimeNodeMetadata {
2981    modifier: Modifier,
2982    resolved_modifiers: ResolvedModifiers,
2983    modifier_slices: Rc<ModifierNodeSlices>,
2984    role: SemanticsRole,
2985    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
2986}
2987
2988impl Default for RuntimeNodeMetadata {
2989    fn default() -> Self {
2990        Self {
2991            modifier: Modifier::empty(),
2992            resolved_modifiers: ResolvedModifiers::default(),
2993            modifier_slices: Rc::default(),
2994            role: SemanticsRole::Unknown,
2995            button_handler: None,
2996        }
2997    }
2998}
2999
3000fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
3001    modifier_slices
3002        .text_content()
3003        .map(|text| SemanticsRole::Text {
3004            value: text.to_string(),
3005        })
3006        .unwrap_or(SemanticsRole::Layout)
3007}
3008
3009fn runtime_metadata_for(
3010    applier: &mut MemoryApplier,
3011    node_id: NodeId,
3012) -> Result<RuntimeNodeMetadata, NodeError> {
3013    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
3014        let modifier = layout.modifier.clone();
3015        let resolved_modifiers = layout.resolved_modifiers();
3016        let modifier_slices = layout.modifier_slices_snapshot();
3017        let role = role_from_modifier_slices(&modifier_slices);
3018
3019        RuntimeNodeMetadata {
3020            modifier,
3021            resolved_modifiers,
3022            modifier_slices,
3023            role,
3024            button_handler: None,
3025        }
3026    }) {
3027        return Ok(meta);
3028    }
3029
3030    if let Ok((modifier, resolved_modifiers, modifier_slices)) = applier
3031        .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
3032            (
3033                node.modifier(),
3034                node.resolved_modifiers(),
3035                node.modifier_slices_snapshot(),
3036            )
3037        })
3038    {
3039        return Ok(RuntimeNodeMetadata {
3040            modifier,
3041            resolved_modifiers,
3042            modifier_slices,
3043            role: SemanticsRole::Subcompose,
3044            button_handler: None,
3045        });
3046    }
3047    Ok(RuntimeNodeMetadata::default())
3048}
3049
3050fn clear_semantics_dirty_flags(
3051    applier: &mut MemoryApplier,
3052    node: &MeasuredNode,
3053) -> Result<(), NodeError> {
3054    match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3055        layout.clear_needs_semantics();
3056    }) {
3057        Ok(()) => {}
3058        Err(NodeError::Missing { .. }) => {}
3059        Err(NodeError::TypeMismatch { .. }) => {
3060            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3061                subcompose.clear_needs_semantics();
3062            }) {
3063                Ok(()) | Err(NodeError::Missing { .. }) | Err(NodeError::TypeMismatch { .. }) => {}
3064                Err(err) => return Err(err),
3065            }
3066        }
3067        Err(err) => return Err(err),
3068    }
3069
3070    for child in &node.children {
3071        clear_semantics_dirty_flags(applier, &child.node)?;
3072    }
3073
3074    Ok(())
3075}
3076
3077fn build_semantics_tree_from_live_nodes(
3078    applier: &mut MemoryApplier,
3079    node: &MeasuredNode,
3080) -> Result<SemanticsTree, NodeError> {
3081    Ok(SemanticsTree::new(build_semantics_node_from_live_nodes(
3082        applier, node,
3083    )?))
3084}
3085
3086fn semantics_node_from_parts(
3087    node_id: NodeId,
3088    mut role: SemanticsRole,
3089    config: Option<SemanticsConfiguration>,
3090    children: Vec<SemanticsNode>,
3091) -> SemanticsNode {
3092    let mut node = SemanticsNode {
3093        node_id,
3094        children,
3095        ..SemanticsNode::default()
3096    };
3097
3098    if let Some(config) = config {
3099        if config.role == Some(SemanticsWidgetRole::Button) {
3100            role = SemanticsRole::Button;
3101        }
3102        if config.is_activatable() {
3103            node.actions.push(SemanticsAction::Click {
3104                handler: SemanticsCallback::new(node_id),
3105            });
3106        }
3107        node.widget_role = config.role;
3108        node.description = config.content_description;
3109        node.state_description = config.state_description;
3110        node.on_click_label = config.on_click_label;
3111        node.on_long_click = config.on_long_click;
3112        node.on_long_click_label = config.on_long_click_label;
3113        node.selected = config.selected;
3114        node.toggled = config.toggled;
3115        node.enabled = config.enabled;
3116        node.custom_actions = config.custom_actions;
3117        node.canvas_children = config.canvas_children;
3118        node.editable_text = config.is_editable_text;
3119        node.hidden = config.hidden;
3120        node.merge_descendants = config.merge_descendants;
3121        node.selectable_group = config.selectable_group;
3122        node.pane_title = config.pane_title;
3123        node.error = config.error;
3124        node.password = config.password;
3125        node.traversal_index = config.traversal_index;
3126        node.text = config.text;
3127        node.text_selection = config.text_selection;
3128        node.live_region = config.live_region;
3129        node.progress = config.progress;
3130        node.set_progress = config.set_progress;
3131        node.set_text = config.set_text;
3132        node.expand = config.expand;
3133        node.collapse = config.collapse;
3134        node.dismiss = config.dismiss;
3135        node.vertical_scroll = config.vertical_scroll;
3136        node.horizontal_scroll = config.horizontal_scroll;
3137        node.scroll_by = config.scroll_by;
3138        node.scroll_to_index = config.scroll_to_index;
3139        node.collection = config.collection;
3140    }
3141
3142    node.focusable = crate::focus_dispatch::has_focus_target(node_id);
3143    node.focused = node.focusable && crate::focus_dispatch::active_focus_target() == Some(node_id);
3144
3145    node.role = role;
3146    node
3147}
3148
3149fn build_semantics_node_from_live_nodes(
3150    applier: &mut MemoryApplier,
3151    node: &MeasuredNode,
3152) -> Result<SemanticsNode, NodeError> {
3153    let (role, config) = match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3154        let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
3155        let config = layout.semantics_configuration();
3156        layout.clear_needs_semantics();
3157        (role, config)
3158    }) {
3159        Ok(data) => data,
3160        Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
3161            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3162                subcompose.clear_needs_semantics();
3163                (
3164                    SemanticsRole::Subcompose,
3165                    collect_semantics_from_modifier(&subcompose.modifier()),
3166                )
3167            }) {
3168                Ok(data) => data,
3169                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
3170                    (SemanticsRole::Unknown, None)
3171                }
3172                Err(err) => return Err(err),
3173            }
3174        }
3175        Err(err) => return Err(err),
3176    };
3177
3178    let mut children = Vec::with_capacity(node.children.len());
3179    for child in &node.children {
3180        children.push(build_semantics_node_from_live_nodes(applier, &child.node)?);
3181    }
3182
3183    Ok(semantics_node_from_parts(
3184        node.node_id,
3185        role,
3186        config,
3187        children,
3188    ))
3189}
3190
3191fn record_semantics_allocation_stats(node: &SemanticsNode, stats: &mut LayoutAllocationDebugStats) {
3192    stats.semantics_node_count += 1;
3193    stats.semantics_action_count += node.actions.len();
3194    stats.semantics_action_capacity += node.actions.capacity();
3195    stats.semantics_child_count += node.children.len();
3196    stats.semantics_child_capacity += node.children.capacity();
3197    stats.semantics_heap_bytes += node.actions.capacity() * size_of::<SemanticsAction>();
3198    stats.semantics_heap_bytes += node.children.capacity() * size_of::<SemanticsNode>();
3199
3200    if let Some(description) = &node.description {
3201        stats.semantics_description_count += 1;
3202        stats.semantics_description_bytes += description.capacity();
3203        stats.semantics_heap_bytes += description.capacity();
3204    }
3205    if let SemanticsRole::Text { value } = &node.role {
3206        stats.semantics_text_role_bytes += value.capacity();
3207        stats.semantics_heap_bytes += value.capacity();
3208    }
3209
3210    for child in &node.children {
3211        record_semantics_allocation_stats(child, stats);
3212    }
3213}
3214
3215fn record_layout_box_allocation_stats(
3216    layout_box: &LayoutBox,
3217    stats: &mut LayoutAllocationDebugStats,
3218) {
3219    stats.layout_box_count += 1;
3220    stats.layout_box_child_count += layout_box.children.len();
3221    stats.layout_box_child_capacity += layout_box.children.capacity();
3222    stats.layout_box_heap_bytes += layout_box.children.capacity() * size_of::<LayoutBox>();
3223    stats.add_modifier_slice(layout_box.node_data.modifier_slices().debug_stats());
3224
3225    for child in &layout_box.children {
3226        record_layout_box_allocation_stats(child, stats);
3227    }
3228}
3229
3230fn build_layout_tree(
3231    applier: &mut MemoryApplier,
3232    node: &MeasuredNode,
3233) -> Result<LayoutTree, NodeError> {
3234    fn place(
3235        applier: &mut MemoryApplier,
3236        node: &MeasuredNode,
3237        origin: Point,
3238        parent_layer_translation: Point,
3239    ) -> Result<LayoutBox, NodeError> {
3240        let top_left = Point {
3241            x: origin.x + node.offset.x,
3242            y: origin.y + node.offset.y,
3243        };
3244        let rect = GeometryRect {
3245            x: top_left.x,
3246            y: top_left.y,
3247            width: node.size.width,
3248            height: node.size.height,
3249        };
3250        let info = runtime_metadata_for(applier, node.node_id)?;
3251        let kind = layout_kind_from_metadata(node.node_id, &info);
3252        let RuntimeNodeMetadata {
3253            modifier,
3254            resolved_modifiers,
3255            modifier_slices,
3256            ..
3257        } = info;
3258
3259        let layer_translation = match modifier_slices.graphics_layer() {
3260            Some(layer) => Point {
3261                x: parent_layer_translation.x + layer.translation_x,
3262                y: parent_layer_translation.y + layer.translation_y,
3263            },
3264            None => parent_layer_translation,
3265        };
3266
3267        if let Some(sink) = modifier_slices.text_field_window_origin() {
3268            sink.set(Point {
3269                x: top_left.x + layer_translation.x,
3270                y: top_left.y + layer_translation.y,
3271            });
3272        }
3273
3274        if let Some(sink) = modifier_slices.viewport_window_rect() {
3275            sink.set(GeometryRect {
3276                x: top_left.x + layer_translation.x,
3277                y: top_left.y + layer_translation.y,
3278                width: node.size.width,
3279                height: node.size.height,
3280            });
3281        }
3282
3283        modifier_slices.publish_pointer_input_size(node.size);
3284
3285        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
3286        let mut children = Vec::with_capacity(node.children.len());
3287        for child in &node.children {
3288            let child_origin = Point {
3289                x: top_left.x + child.offset.x,
3290                y: top_left.y + child.offset.y,
3291            };
3292            children.push(place(
3293                applier,
3294                &child.node,
3295                child_origin,
3296                layer_translation,
3297            )?);
3298        }
3299        Ok(LayoutBox::new(
3300            node.node_id,
3301            rect,
3302            node.content_offset,
3303            data,
3304            children,
3305        ))
3306    }
3307
3308    Ok(LayoutTree::new(place(
3309        applier,
3310        node,
3311        Point { x: 0.0, y: 0.0 },
3312        Point { x: 0.0, y: 0.0 },
3313    )?))
3314}
3315
3316fn semantics_role_from_layout_box(layout_box: &LayoutBox) -> SemanticsRole {
3317    match &layout_box.node_data.kind {
3318        LayoutNodeKind::Subcompose => SemanticsRole::Subcompose,
3319        LayoutNodeKind::Spacer => SemanticsRole::Spacer,
3320        LayoutNodeKind::Unknown => SemanticsRole::Unknown,
3321        LayoutNodeKind::Button { .. } => SemanticsRole::Button,
3322        LayoutNodeKind::Layout => layout_box
3323            .node_data
3324            .modifier_slices()
3325            .text_content()
3326            .map(|text| SemanticsRole::Text {
3327                value: text.to_string(),
3328            })
3329            .unwrap_or(SemanticsRole::Layout),
3330    }
3331}
3332
3333fn build_semantics_node_from_layout_box(layout_box: &LayoutBox) -> SemanticsNode {
3334    let children = layout_box
3335        .children
3336        .iter()
3337        .map(build_semantics_node_from_layout_box)
3338        .collect();
3339
3340    semantics_node_from_parts(
3341        layout_box.node_id,
3342        semantics_role_from_layout_box(layout_box),
3343        collect_semantics_from_modifier(&layout_box.node_data.modifier),
3344        children,
3345    )
3346}
3347
3348fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
3349    match &info.role {
3350        SemanticsRole::Layout => LayoutNodeKind::Layout,
3351        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
3352        SemanticsRole::Text { .. } => LayoutNodeKind::Layout,
3353        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
3354        SemanticsRole::Button => {
3355            let handler = info
3356                .button_handler
3357                .as_ref()
3358                .cloned()
3359                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
3360            LayoutNodeKind::Button { on_click: handler }
3361        }
3362        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
3363    }
3364}
3365
3366fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
3367    let horizontal = padding.horizontal_sum();
3368    let vertical = padding.vertical_sum();
3369    let min_width = (constraints.min_width - horizontal).max(0.0);
3370    let mut max_width = constraints.max_width;
3371    if max_width.is_finite() {
3372        max_width = (max_width - horizontal).max(0.0);
3373    }
3374    let min_height = (constraints.min_height - vertical).max(0.0);
3375    let mut max_height = constraints.max_height;
3376    if max_height.is_finite() {
3377        max_height = (max_height - vertical).max(0.0);
3378    }
3379    normalize_constraints(Constraints {
3380        min_width,
3381        max_width,
3382        min_height,
3383        max_height,
3384    })
3385}
3386
3387#[cfg(test)]
3388pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
3389    match alignment {
3390        HorizontalAlignment::Start => 0.0,
3391        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
3392        HorizontalAlignment::End => (available - child).max(0.0),
3393    }
3394}
3395
3396#[cfg(test)]
3397pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
3398    match alignment {
3399        VerticalAlignment::Top => 0.0,
3400        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
3401        VerticalAlignment::Bottom => (available - child).max(0.0),
3402    }
3403}
3404
3405fn resolve_dimension(
3406    base: f32,
3407    explicit: DimensionConstraint,
3408    min_override: Option<f32>,
3409    max_override: Option<f32>,
3410    min_limit: f32,
3411    max_limit: f32,
3412) -> f32 {
3413    let mut min_bound = min_limit;
3414    if let Some(min_value) = min_override {
3415        min_bound = min_bound.max(min_value);
3416    }
3417
3418    let mut max_bound = if max_limit.is_finite() {
3419        max_limit
3420    } else {
3421        max_override.unwrap_or(max_limit)
3422    };
3423    if let Some(max_value) = max_override {
3424        if max_bound.is_finite() {
3425            max_bound = max_bound.min(max_value);
3426        } else {
3427            max_bound = max_value;
3428        }
3429    }
3430    if max_bound < min_bound {
3431        max_bound = min_bound;
3432    }
3433
3434    let mut size = match explicit {
3435        DimensionConstraint::Points(points) => points,
3436        DimensionConstraint::Fraction(fraction) => {
3437            if max_limit.is_finite() {
3438                max_limit * fraction.clamp(0.0, 1.0)
3439            } else {
3440                base
3441            }
3442        }
3443        DimensionConstraint::Unspecified => base,
3444        DimensionConstraint::Intrinsic(_) => base,
3445    };
3446
3447    size = clamp_dimension(size, min_bound, max_bound);
3448    size = clamp_dimension(size, min_limit, max_limit);
3449    size.max(0.0)
3450}
3451
3452fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
3453    let mut result = value.max(min);
3454    if max.is_finite() {
3455        result = result.min(max);
3456    }
3457    result
3458}
3459
3460fn normalize_constraints(mut constraints: Constraints) -> Constraints {
3461    if constraints.max_width < constraints.min_width {
3462        constraints.max_width = constraints.min_width;
3463    }
3464    if constraints.max_height < constraints.min_height {
3465        constraints.max_height = constraints.min_height;
3466    }
3467    constraints
3468}
3469
3470#[cfg(test)]
3471#[path = "tests/layout_tests.rs"]
3472mod tests;