Skip to main content

cranpose_ui/layout/
mod.rs

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