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        } else {
1462            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1463                node.set_measured_size(measured.size);
1464            });
1465        }
1466
1467        Ok(Some(measured))
1468    }
1469
1470    fn try_measure_subcompose(
1471        state_rc: Rc<RefCell<Self>>,
1472        node_id: NodeId,
1473        constraints: Constraints,
1474    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1475        let applier_host = {
1476            let state = state_rc.borrow();
1477            Rc::clone(&state.applier)
1478        };
1479
1480        let (node_handle, resolved_modifiers) = {
1481            let Ok(mut applier) = applier_host.try_borrow_typed() else {
1482                return Ok(None);
1483            };
1484            let node = match applier.get_mut(node_id) {
1485                Ok(node) => node,
1486                Err(NodeError::Missing { .. }) => return Ok(None),
1487                Err(err) => return Err(err),
1488            };
1489            let any = node.as_any_mut();
1490            if let Some(subcompose) =
1491                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1492            {
1493                let handle = subcompose.handle();
1494                let resolved_modifiers = handle.resolved_modifiers();
1495                (handle, resolved_modifiers)
1496            } else {
1497                return Ok(None);
1498            }
1499        };
1500
1501        let runtime_handle = {
1502            let mut state = state_rc.borrow_mut();
1503            if state.runtime_handle.is_none()
1504                && let Ok(applier) = applier_host.try_borrow_typed()
1505            {
1506                state.runtime_handle = applier.runtime_handle();
1507            }
1508            state
1509                .runtime_handle
1510                .clone()
1511                .ok_or(NodeError::MissingContext {
1512                    id: node_id,
1513                    reason: "runtime handle required for subcomposition",
1514                })?
1515        };
1516
1517        let props = resolved_modifiers.layout_properties();
1518        let padding = resolved_modifiers.padding();
1519        let offset = resolved_modifiers.offset();
1520        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
1521
1522        if let DimensionConstraint::Points(width) = props.width() {
1523            let constrained_width = width - padding.horizontal_sum();
1524            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
1525            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
1526        }
1527        if let DimensionConstraint::Points(height) = props.height() {
1528            let constrained_height = height - padding.vertical_sum();
1529            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
1530            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
1531        }
1532
1533        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
1534        let slots_host = slots_guard.host();
1535        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
1536        let observer = SnapshotStateObserver::new(|callback| callback());
1537        let composer = Composer::new(
1538            Rc::clone(&slots_host),
1539            applier_host_dyn,
1540            runtime_handle,
1541            observer,
1542            Some(node_id),
1543        );
1544        composer.enter_phase(Phase::Measure);
1545
1546        let state_rc_clone = Rc::clone(&state_rc);
1547        let measure_error = RefCell::new(None);
1548        let state_rc_for_subcompose = Rc::clone(&state_rc_clone);
1549        let error_for_subcompose = &measure_error;
1550        let measured_children = node_handle.measured_children_scratch();
1551        let measured_children_for_subcompose = Rc::clone(&measured_children);
1552        let state_rc_for_cached = Rc::clone(&state_rc_clone);
1553        let error_for_cached = &measure_error;
1554        let measured_children_for_cached = Rc::clone(&measured_children);
1555        let measured_children_for_lookup = Rc::clone(&measured_children);
1556        let measured_children_for_retained = Rc::clone(&measured_children);
1557
1558        let measure_result = node_handle.measure_with_cached_batch(
1559            &composer,
1560            node_id,
1561            inner_constraints,
1562            CachedBatchMeasureInputs {
1563                measurer: Box::new(
1564                    move |child_id: NodeId, child_constraints: Constraints| -> Size {
1565                        match Self::measure_node(
1566                            Rc::clone(&state_rc_for_subcompose),
1567                            child_id,
1568                            child_constraints,
1569                        ) {
1570                            Ok(measured) => {
1571                                measured_children_for_subcompose
1572                                    .borrow_mut()
1573                                    .insert(child_id, Rc::clone(&measured));
1574                                measured.size
1575                            }
1576                            Err(err) => {
1577                                let mut slot = error_for_subcompose.borrow_mut();
1578                                if slot.is_none() {
1579                                    *slot = Some(err);
1580                                }
1581                                Size::default()
1582                            }
1583                        }
1584                    },
1585                ),
1586                cached_measure_batch_registrar: Box::new(
1587                    move |child_ids: &[NodeId],
1588                          child_constraints: Constraints,
1589                          out: &mut Vec<Option<Size>>| {
1590                        out.clear();
1591                        out.resize(child_ids.len(), None);
1592
1593                        let applier_host = {
1594                            let state = state_rc_for_cached.borrow();
1595                            Rc::clone(&state.applier)
1596                        };
1597                        let Ok(mut applier) = applier_host.try_borrow_typed() else {
1598                            return;
1599                        };
1600
1601                        let mut measured_children = measured_children_for_cached.borrow_mut();
1602                        for (index, &child_id) in child_ids.iter().enumerate() {
1603                            match Self::cached_measure_node_with_applier(
1604                                &mut applier,
1605                                child_id,
1606                                child_constraints,
1607                            ) {
1608                                Ok(Some(measured)) => {
1609                                    out[index] = Some(measured.size);
1610                                    measured_children.insert(child_id, Rc::clone(&measured));
1611                                }
1612                                Ok(None) => {}
1613                                Err(err) => {
1614                                    let mut slot = error_for_cached.borrow_mut();
1615                                    if slot.is_none() {
1616                                        *slot = Some(err);
1617                                    }
1618                                    break;
1619                                }
1620                            }
1621                        }
1622                    },
1623                ),
1624                retained_measure_lookup: Box::new(move |child_id| {
1625                    measured_children_for_lookup
1626                        .borrow()
1627                        .get(&child_id)
1628                        .cloned()
1629                }),
1630                retained_measure_registrar: Box::new(move |measurements| {
1631                    let mut measured_children = measured_children_for_retained.borrow_mut();
1632                    for measured in measurements {
1633                        measured_children.insert(measured.node_id(), Rc::clone(measured));
1634                    }
1635                }),
1636                error: &measure_error,
1637            },
1638        )?;
1639        drop(composer);
1640        slots_guard.restore(slots_host.into_table()?);
1641
1642        if let Some(err) = measure_error.borrow_mut().take() {
1643            return Err(err);
1644        }
1645
1646        let cranpose_ui_layout::MeasureResult {
1647            size: measured_size,
1648            placements,
1649        } = measure_result;
1650
1651        let mut width = measured_size.width + padding.horizontal_sum();
1652        let mut height = measured_size.height + padding.vertical_sum();
1653
1654        width = resolve_dimension(
1655            width,
1656            props.width(),
1657            props.min_width(),
1658            props.max_width(),
1659            constraints.min_width,
1660            constraints.max_width,
1661        );
1662        height = resolve_dimension(
1663            height,
1664            props.height(),
1665            props.min_height(),
1666            props.max_height(),
1667            constraints.min_height,
1668            constraints.max_height,
1669        );
1670
1671        let mut children = Vec::with_capacity(placements.len());
1672        let mut measured_children_by_id = measured_children.borrow_mut();
1673
1674        if let Ok(mut applier) = applier_host.try_borrow_typed() {
1675            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
1676                parent_node.set_measured_size(Size { width, height });
1677                parent_node.clear_needs_measure();
1678                parent_node.clear_needs_layout();
1679            });
1680        }
1681
1682        for placement in &placements {
1683            let child = if let Some(measured) = measured_children_by_id.remove(&placement.node_id) {
1684                measured
1685            } else {
1686                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?
1687            };
1688            let policy_position = Point {
1689                x: padding.left + placement.x,
1690                y: padding.top + placement.y,
1691            };
1692            let retained_position = Point {
1693                x: policy_position.x + child.offset.x,
1694                y: policy_position.y + child.offset.y,
1695            };
1696
1697            if let Ok(mut applier) = applier_host.try_borrow_typed()
1698                && applier
1699                    .with_node::<LayoutNode, _>(placement.node_id, |node| {
1700                        node.set_position(retained_position);
1701                    })
1702                    .is_err()
1703            {
1704                let _ = applier.with_node::<SubcomposeLayoutNode, _>(placement.node_id, |node| {
1705                    node.set_position(retained_position);
1706                });
1707            }
1708
1709            children.push(MeasuredChild {
1710                node: child,
1711                offset: policy_position,
1712            });
1713        }
1714
1715        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1716        node_handle.recycle_placement_scratch(placements);
1717
1718        Ok(Some(Rc::new(MeasuredNode::new(
1719            node_id,
1720            Size { width, height },
1721            offset,
1722            Point::default(),
1723            children,
1724        ))))
1725    }
1726    fn measure_through_modifier_chain(
1727        state_rc: &Rc<RefCell<Self>>,
1728        node_id: NodeId,
1729        runtime_state: &mut LayoutRuntimeState,
1730        measure_policy: &Rc<dyn MeasurePolicy>,
1731        constraints: Constraints,
1732        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1733        placements: &mut Vec<Placement>,
1734    ) -> ModifierChainMeasurement {
1735        use cranpose_foundation::NodeCapabilities;
1736
1737        layout_node_data.clear();
1738        let mut offset = Point::default();
1739        let mut density = crate::density::Density::default();
1740        let mut window_root = false;
1741
1742        {
1743            let state = state_rc.borrow();
1744            let mut applier = state.applier.borrow_typed();
1745
1746            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1747                density = layout_node.density();
1748                window_root = layout_node.is_window_root();
1749                let chain_handle = layout_node.modifier_chain();
1750
1751                if !chain_handle.has_layout_nodes() {
1752                    return;
1753                }
1754
1755                chain_handle.chain().for_each_forward_matching(
1756                    NodeCapabilities::LAYOUT,
1757                    |node_ref| {
1758                        if let Some(index) = node_ref.entry_index() {
1759                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1760                                layout_node_data.push((index, Rc::clone(&node_rc)));
1761                            }
1762
1763                            node_ref.with_node(|node| {
1764                                if let Some(offset_node) =
1765                                    node.as_any()
1766                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1767                                {
1768                                    let delta = offset_node.offset();
1769                                    offset.x += delta.x;
1770                                    offset.y += delta.y;
1771                                }
1772                            });
1773                        }
1774                    },
1775                );
1776            });
1777        }
1778
1779        let scope = crate::density::DensityMeasureScope::new(density);
1780
1781        if layout_node_data.is_empty() {
1782            let final_size = measure_policy.measure_into(
1783                &scope,
1784                runtime_state.child_measurables(),
1785                constraints,
1786                placements,
1787            );
1788
1789            return ModifierChainMeasurement {
1790                size: final_size,
1791                content_offset: Point::default(),
1792                offset,
1793                window_root,
1794            };
1795        }
1796
1797        runtime_state.reconcile_coordinator_chain(layout_node_data.as_slice());
1798        let frame = CoordinatorFrame::new(
1799            measure_policy,
1800            &scope,
1801            runtime_state.child_measurables(),
1802            placements,
1803        );
1804
1805        let placeable = runtime_state
1806            .coordinator_chain()
1807            .measure_from(0, &frame, constraints);
1808        let final_size = Size {
1809            width: placeable.width(),
1810            height: placeable.height(),
1811        };
1812
1813        let content_offset = placeable.content_offset();
1814        let all_placement_offset = Point {
1815            x: content_offset.0,
1816            y: content_offset.1,
1817        };
1818
1819        let content_offset = Point {
1820            x: all_placement_offset.x - offset.x,
1821            y: all_placement_offset.y - offset.y,
1822        };
1823
1824        let invalidations = frame.take_invalidations();
1825        if !invalidations.is_empty() {
1826            Self::with_applier_result(state_rc, |applier| {
1827                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1828                    for kind in invalidations {
1829                        match kind {
1830                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1831                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1832                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1833                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1834                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1835                        }
1836                    }
1837                })
1838            })
1839            .ok();
1840        }
1841
1842        ModifierChainMeasurement {
1843            size: final_size,
1844            content_offset,
1845            offset,
1846            window_root,
1847        }
1848    }
1849
1850    fn layout_child_measure_data(
1851        applier: &mut MemoryApplier,
1852        child_id: NodeId,
1853    ) -> Result<Option<LayoutChildMeasureData>, NodeError> {
1854        match applier.with_node::<LayoutNode, _>(child_id, |n| LayoutChildMeasureData {
1855            cache: n.cache_handles(),
1856            layout_state: Some(n.layout_state_handle()),
1857            needs_layout: n.needs_layout(),
1858            needs_measure: n.needs_measure(),
1859        }) {
1860            Ok(data) => Ok(Some(data)),
1861            Err(NodeError::TypeMismatch { .. }) => {
1862                match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |n| {
1863                    LayoutChildMeasureData {
1864                        cache: n.cache_handles(),
1865                        layout_state: None,
1866                        needs_layout: n.needs_layout(),
1867                        needs_measure: n.needs_measure(),
1868                    }
1869                }) {
1870                    Ok(data) => Ok(Some(data)),
1871                    Err(NodeError::TypeMismatch { .. } | NodeError::Missing { .. }) => Ok(None),
1872                    Err(err) => Err(err),
1873                }
1874            }
1875            Err(NodeError::Missing { .. }) => Ok(None),
1876            Err(err) => Err(err),
1877        }
1878    }
1879
1880    fn measure_layout_node(
1881        state_rc: Rc<RefCell<Self>>,
1882        node_id: NodeId,
1883        snapshot: LayoutNodeSnapshot,
1884        constraints: Constraints,
1885    ) -> Result<Rc<MeasuredNode>, NodeError> {
1886        let (cache_epoch, cache_floor) = {
1887            let state = state_rc.borrow();
1888            (state.cache_epoch, state.cache_floor)
1889        };
1890        let LayoutNodeSnapshot {
1891            measure_policy,
1892            cache,
1893            layout_runtime_state,
1894            needs_layout,
1895            needs_measure,
1896        } = snapshot;
1897        cache.activate(cache_epoch);
1898
1899        if !needs_measure
1900            && !needs_layout
1901            && let Some(cached) = cache.get_measurement(constraints)
1902        {
1903            Self::with_applier_result(&state_rc, |applier| {
1904                applier.with_node::<LayoutNode, _>(node_id, |node| {
1905                    node.clear_needs_measure();
1906                    node.clear_needs_layout();
1907                })
1908            })
1909            .ok();
1910            return Ok(cached);
1911        }
1912
1913        let (runtime_handle, applier_host) = {
1914            let state = state_rc.borrow();
1915            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1916        };
1917
1918        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1919        let error = Rc::new(RefCell::new(None));
1920        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1921        let (records, child_ids, layout_node_data, placements) = pools.parts();
1922
1923        applier_host
1924            .borrow_typed()
1925            .with_node::<LayoutNode, _>(node_id, |node| {
1926                child_ids.extend_from_slice(&node.children);
1927            })?;
1928
1929        let mut valid_child_count = 0;
1930        for index in 0..child_ids.len() {
1931            let child_id = child_ids[index];
1932            let child_exists = {
1933                let mut applier = applier_host.borrow_typed();
1934                Self::layout_child_measure_data(&mut applier, child_id)?.is_some()
1935            };
1936            if child_exists {
1937                child_ids[valid_child_count] = child_id;
1938                valid_child_count += 1;
1939            }
1940        }
1941        child_ids.truncate(valid_child_count);
1942
1943        let _frame_binding_cleanup =
1944            LayoutRuntimeFrameBindingCleanup::new(Rc::clone(&layout_runtime_state));
1945
1946        {
1947            let mut runtime_state = layout_runtime_state.borrow_mut();
1948            runtime_state.reconcile_child_measurables(child_ids.as_slice());
1949
1950            for (index, &child_id) in child_ids.iter().enumerate() {
1951                let data = {
1952                    let mut applier = applier_host.borrow_typed();
1953                    Self::layout_child_measure_data(&mut applier, child_id)?
1954                };
1955                let Some(data) = data else {
1956                    continue;
1957                };
1958
1959                let child_is_stale = data.is_stale(cache_floor);
1960                let child_cache_epoch = if child_is_stale {
1961                    cache_epoch
1962                } else {
1963                    data.cache.epoch()
1964                };
1965                let child_state = runtime_state.child_state(index);
1966                child_state.configure(LayoutChildMeasureConfig {
1967                    applier: Rc::clone(&applier_host),
1968                    node_id: child_id,
1969                    error: Rc::clone(&error),
1970                    runtime_handle: runtime_handle.clone(),
1971                    cache: data.cache,
1972                    cache_epoch: child_cache_epoch,
1973                    force_remeasure: child_is_stale,
1974                    measure_handle: Some(measure_handle.clone()),
1975                    layout_state: data.layout_state,
1976                });
1977                records.push((child_id, ChildRecord { state: child_state }));
1978            }
1979        }
1980
1981        let chain_constraints = constraints;
1982
1983        let modifier_chain_result = {
1984            let mut runtime_state = layout_runtime_state.borrow_mut();
1985            Self::measure_through_modifier_chain(
1986                &state_rc,
1987                node_id,
1988                &mut runtime_state,
1989                &measure_policy,
1990                chain_constraints,
1991                layout_node_data,
1992                placements,
1993            )
1994        };
1995
1996        let (width, height, content_offset, offset, window_root) = {
1997            let result = modifier_chain_result;
1998            if let Some(err) = error.borrow_mut().take() {
1999                return Err(err);
2000            }
2001
2002            (
2003                result.size.width,
2004                result.size.height,
2005                result.content_offset,
2006                result.offset,
2007                result.window_root,
2008            )
2009        };
2010
2011        let mut measured_children = Vec::with_capacity(records.len());
2012        for (child_id, record) in records.iter() {
2013            if let Some(measured) = record.state.take_measured() {
2014                let placed = placements
2015                    .iter()
2016                    .find(|placement| placement.node_id == *child_id)
2017                    .map(|placement| Point {
2018                        x: placement.x,
2019                        y: placement.y,
2020                    });
2021                if let Some(raw) = placed {
2022                    record.state.place_retained(Point {
2023                        x: raw.x + measured.offset.x,
2024                        y: raw.y + measured.offset.y,
2025                    });
2026                }
2027                let base_position = placed
2028                    .or_else(|| record.state.last_position())
2029                    .unwrap_or(Point { x: 0.0, y: 0.0 });
2030                let position = Point {
2031                    x: content_offset.x + base_position.x,
2032                    y: content_offset.y + base_position.y,
2033                };
2034                measured_children.push(MeasuredChild {
2035                    node: measured,
2036                    offset: position,
2037                });
2038            }
2039        }
2040
2041        let measured = Rc::new(
2042            MeasuredNode::new(
2043                node_id,
2044                Size { width, height },
2045                offset,
2046                content_offset,
2047                measured_children,
2048            )
2049            .with_window_root(window_root),
2050        );
2051
2052        cache.store_measurement(constraints, Rc::clone(&measured));
2053
2054        Self::with_applier_result(&state_rc, |applier| {
2055            applier.with_node::<LayoutNode, _>(node_id, |node| {
2056                node.clear_needs_measure();
2057                node.clear_needs_layout();
2058                node.set_measured_size(Size { width, height });
2059                node.set_content_offset(content_offset);
2060            })
2061        })
2062        .ok();
2063
2064        Ok(measured)
2065    }
2066}
2067
2068struct LayoutChildMeasureData {
2069    cache: LayoutNodeCacheHandles,
2070    layout_state: Option<Rc<RefCell<LayoutState>>>,
2071    needs_layout: bool,
2072    needs_measure: bool,
2073}
2074
2075impl LayoutChildMeasureData {
2076    fn is_stale(&self, cache_floor: u64) -> bool {
2077        self.needs_layout || self.needs_measure || self.cache.epoch() < cache_floor
2078    }
2079}
2080
2081struct LayoutNodeSnapshot {
2082    measure_policy: Rc<dyn MeasurePolicy>,
2083    cache: LayoutNodeCacheHandles,
2084    layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
2085    needs_layout: bool,
2086    needs_measure: bool,
2087}
2088
2089impl LayoutNodeSnapshot {
2090    fn from_layout_node(node: &LayoutNode) -> Self {
2091        Self {
2092            measure_policy: Rc::clone(&node.measure_policy),
2093            cache: node.cache_handles(),
2094            layout_runtime_state: node.layout_runtime_state_handle(),
2095            needs_layout: node.needs_layout(),
2096            needs_measure: node.needs_measure(),
2097        }
2098    }
2099}
2100
2101struct VecPools {
2102    state: Rc<RefCell<LayoutBuilderState>>,
2103    records: Vec<(NodeId, ChildRecord)>,
2104    child_ids: Vec<NodeId>,
2105    layout_node_data: Vec<LayoutModifierNodeData>,
2106    placements: Vec<Placement>,
2107}
2108
2109impl VecPools {
2110    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2111        let (records, child_ids, layout_node_data, placements) = {
2112            let mut state_mut = state.borrow_mut();
2113            (
2114                state_mut.frame_arena.tmp_records.acquire(),
2115                state_mut.frame_arena.tmp_child_ids.acquire(),
2116                state_mut.frame_arena.tmp_layout_node_data.acquire(),
2117                state_mut.frame_arena.tmp_placements.acquire(),
2118            )
2119        };
2120        Self {
2121            state,
2122            records,
2123            child_ids,
2124            layout_node_data,
2125            placements,
2126        }
2127    }
2128
2129    #[expect(clippy::type_complexity)]
2130    fn parts(
2131        &mut self,
2132    ) -> (
2133        &mut Vec<(NodeId, ChildRecord)>,
2134        &mut Vec<NodeId>,
2135        &mut Vec<LayoutModifierNodeData>,
2136        &mut Vec<Placement>,
2137    ) {
2138        (
2139            &mut self.records,
2140            &mut self.child_ids,
2141            &mut self.layout_node_data,
2142            &mut self.placements,
2143        )
2144    }
2145}
2146
2147impl Drop for VecPools {
2148    fn drop(&mut self) {
2149        let mut state = self.state.borrow_mut();
2150        state
2151            .frame_arena
2152            .tmp_records
2153            .release(std::mem::take(&mut self.records));
2154        state
2155            .frame_arena
2156            .tmp_child_ids
2157            .release(std::mem::take(&mut self.child_ids));
2158        state
2159            .frame_arena
2160            .tmp_layout_node_data
2161            .release(std::mem::take(&mut self.layout_node_data));
2162        state
2163            .frame_arena
2164            .tmp_placements
2165            .release(std::mem::take(&mut self.placements));
2166    }
2167}
2168
2169struct SlotsGuard {
2170    state: Rc<RefCell<LayoutBuilderState>>,
2171    slots: Option<SlotTable>,
2172}
2173
2174impl SlotsGuard {
2175    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2176        let slots = {
2177            let state_ref = state.borrow();
2178            let mut slots_ref = state_ref.slots.borrow_mut();
2179            std::mem::take(&mut *slots_ref)
2180        };
2181        Self {
2182            state,
2183            slots: Some(slots),
2184        }
2185    }
2186
2187    fn host(&mut self) -> Rc<SlotsHost> {
2188        let slots = self.slots.take().unwrap_or_default();
2189        Rc::new(SlotsHost::new(slots))
2190    }
2191
2192    fn restore(&mut self, slots: SlotTable) {
2193        debug_assert!(self.slots.is_none());
2194        self.slots = Some(slots);
2195    }
2196}
2197
2198impl Drop for SlotsGuard {
2199    fn drop(&mut self) {
2200        if let Some(slots) = self.slots.take() {
2201            let state_ref = self.state.borrow();
2202            *state_ref.slots.borrow_mut() = slots;
2203        }
2204    }
2205}
2206
2207#[derive(Clone)]
2208struct LayoutMeasureHandle {
2209    state: Rc<RefCell<LayoutBuilderState>>,
2210}
2211
2212impl LayoutMeasureHandle {
2213    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2214        Self { state }
2215    }
2216
2217    fn measure(
2218        &self,
2219        node_id: NodeId,
2220        constraints: Constraints,
2221    ) -> Result<Rc<MeasuredNode>, NodeError> {
2222        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
2223    }
2224}
2225
2226#[derive(Debug, Clone)]
2227pub(crate) struct MeasuredNode {
2228    node_id: NodeId,
2229    size: Size,
2230    offset: Point,
2231    content_offset: Point,
2232    children: Vec<MeasuredChild>,
2233    window_root: bool,
2234}
2235
2236impl MeasuredNode {
2237    fn new(
2238        node_id: NodeId,
2239        size: Size,
2240        offset: Point,
2241        content_offset: Point,
2242        children: Vec<MeasuredChild>,
2243    ) -> Self {
2244        Self {
2245            node_id,
2246            size,
2247            offset,
2248            content_offset,
2249            children,
2250            window_root: false,
2251        }
2252    }
2253
2254    fn with_window_root(mut self, window_root: bool) -> Self {
2255        self.window_root = window_root;
2256        self
2257    }
2258
2259    pub(crate) fn size_for_parent(&self) -> Size {
2260        if self.window_root {
2261            Size::new(0.0, 0.0)
2262        } else {
2263            self.size
2264        }
2265    }
2266
2267    #[cfg(test)]
2268    pub(crate) fn leaf(node_id: NodeId, size: Size) -> Self {
2269        Self::new(
2270            node_id,
2271            size,
2272            Point::default(),
2273            Point::default(),
2274            Vec::new(),
2275        )
2276    }
2277
2278    pub(crate) fn node_id(&self) -> NodeId {
2279        self.node_id
2280    }
2281
2282    pub(crate) fn size(&self) -> Size {
2283        self.size
2284    }
2285}
2286
2287#[derive(Debug, Clone)]
2288struct MeasuredChild {
2289    node: Rc<MeasuredNode>,
2290    offset: Point,
2291}
2292
2293struct ChildRecord {
2294    state: Rc<LayoutChildMeasureState>,
2295}
2296
2297struct CoordinatorFrame<'a> {
2298    measure_policy: &'a Rc<dyn MeasurePolicy>,
2299    scope: &'a dyn cranpose_ui_layout::MeasureScope,
2300    measurables: &'a [Box<dyn Measurable>],
2301    placements: RefCell<&'a mut Vec<Placement>>,
2302    context: RefCell<LayoutNodeContext>,
2303}
2304
2305impl<'a> CoordinatorFrame<'a> {
2306    fn new(
2307        measure_policy: &'a Rc<dyn MeasurePolicy>,
2308        scope: &'a dyn cranpose_ui_layout::MeasureScope,
2309        measurables: &'a [Box<dyn Measurable>],
2310        placements: &'a mut Vec<Placement>,
2311    ) -> Self {
2312        Self {
2313            measure_policy,
2314            scope,
2315            measurables,
2316            placements: RefCell::new(placements),
2317            context: RefCell::new(LayoutNodeContext::new()),
2318        }
2319    }
2320
2321    fn take_invalidations(&self) -> Vec<InvalidationKind> {
2322        self.context.borrow_mut().take_invalidations()
2323    }
2324}
2325
2326struct CoordinatorLink<'chain, 'frame_ref, 'frame_data> {
2327    chain: &'chain CoordinatorChain,
2328    frame: &'frame_ref CoordinatorFrame<'frame_data>,
2329    index: usize,
2330}
2331
2332impl Measurable for CoordinatorLink<'_, '_, '_> {
2333    fn measure(&self, constraints: Constraints) -> Placeable {
2334        self.chain.measure_from(self.index, self.frame, constraints)
2335    }
2336
2337    fn min_intrinsic_width(&self, height: f32) -> f32 {
2338        self.chain
2339            .min_intrinsic_width_from(self.index, self.frame, height)
2340    }
2341
2342    fn max_intrinsic_width(&self, height: f32) -> f32 {
2343        self.chain
2344            .max_intrinsic_width_from(self.index, self.frame, height)
2345    }
2346
2347    fn min_intrinsic_height(&self, width: f32) -> f32 {
2348        self.chain
2349            .min_intrinsic_height_from(self.index, self.frame, width)
2350    }
2351
2352    fn max_intrinsic_height(&self, width: f32) -> f32 {
2353        self.chain
2354            .max_intrinsic_height_from(self.index, self.frame, width)
2355    }
2356}
2357
2358struct CoordinatorNode {
2359    modifier_index: usize,
2360    node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2361    measured_size: Cell<Size>,
2362    accumulated_offset: Cell<Point>,
2363}
2364
2365impl CoordinatorNode {
2366    fn new(
2367        modifier_index: usize,
2368        node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2369    ) -> Self {
2370        Self {
2371            modifier_index,
2372            node,
2373            measured_size: Cell::new(Size::default()),
2374            accumulated_offset: Cell::new(Point::default()),
2375        }
2376    }
2377
2378    fn matches(
2379        &self,
2380        modifier_index: usize,
2381        node: &Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2382    ) -> bool {
2383        self.modifier_index == modifier_index && Rc::ptr_eq(&self.node, node)
2384    }
2385
2386    #[cfg(test)]
2387    fn ptr(&self) -> usize {
2388        Rc::as_ptr(&self.node) as *const () as usize
2389    }
2390}
2391
2392#[derive(Default)]
2393struct CoordinatorChain {
2394    nodes: Vec<CoordinatorNode>,
2395}
2396
2397impl CoordinatorChain {
2398    fn reconcile(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2399        if self.matches(layout_node_data) {
2400            return;
2401        }
2402
2403        let mut previous_nodes = std::mem::take(&mut self.nodes);
2404        self.nodes.reserve(layout_node_data.len());
2405
2406        for (modifier_index, node) in layout_node_data {
2407            if let Some(position) = previous_nodes
2408                .iter()
2409                .position(|candidate| candidate.matches(*modifier_index, node))
2410            {
2411                self.nodes.push(previous_nodes.swap_remove(position));
2412            } else {
2413                self.nodes
2414                    .push(CoordinatorNode::new(*modifier_index, Rc::clone(node)));
2415            }
2416        }
2417    }
2418
2419    fn matches(&self, layout_node_data: &[LayoutModifierNodeData]) -> bool {
2420        self.nodes.len() == layout_node_data.len()
2421            && self
2422                .nodes
2423                .iter()
2424                .zip(layout_node_data.iter())
2425                .all(|(node, (modifier_index, node_rc))| node.matches(*modifier_index, node_rc))
2426    }
2427
2428    fn measure_from(
2429        &self,
2430        index: usize,
2431        frame: &CoordinatorFrame<'_>,
2432        constraints: Constraints,
2433    ) -> Placeable {
2434        let Some(node) = self.nodes.get(index) else {
2435            let mut placements = frame.placements.borrow_mut();
2436            let size = frame.measure_policy.measure_into(
2437                frame.scope,
2438                frame.measurables,
2439                constraints,
2440                &mut placements,
2441            );
2442            return Placeable::value(size.width, size.height, NodeId::default());
2443        };
2444
2445        let wrapped = CoordinatorLink {
2446            chain: self,
2447            frame,
2448            index: index + 1,
2449        };
2450        let node_borrow = node.node.borrow();
2451
2452        let Some(layout_node) = node_borrow.as_layout_node() else {
2453            let placeable = wrapped.measure(constraints);
2454            let child_accumulated = self.total_content_offset_from(index + 1);
2455            node.accumulated_offset.set(child_accumulated);
2456            return Placeable::value_with_offset(
2457                placeable.width(),
2458                placeable.height(),
2459                NodeId::default(),
2460                (child_accumulated.x, child_accumulated.y),
2461            );
2462        };
2463
2464        let result = match frame.context.try_borrow_mut() {
2465            Ok(mut context) => layout_node.measure(&mut *context, &wrapped, constraints),
2466            Err(_) => {
2467                let mut temp = LayoutNodeContext::new();
2468                let result = layout_node.measure(&mut temp, &wrapped, constraints);
2469                if let Ok(mut context) = frame.context.try_borrow_mut() {
2470                    for kind in temp.take_invalidations() {
2471                        context.invalidate(kind);
2472                    }
2473                }
2474                result
2475            }
2476        };
2477
2478        node.measured_size.set(result.size);
2479        let local_offset = Point {
2480            x: result.placement_offset_x,
2481            y: result.placement_offset_y,
2482        };
2483        let child_accumulated = self.total_content_offset_from(index + 1);
2484        let accumulated = Point {
2485            x: local_offset.x + child_accumulated.x,
2486            y: local_offset.y + child_accumulated.y,
2487        };
2488        node.accumulated_offset.set(accumulated);
2489
2490        Placeable::value_with_offset(
2491            result.size.width,
2492            result.size.height,
2493            NodeId::default(),
2494            (accumulated.x, accumulated.y),
2495        )
2496    }
2497
2498    fn min_intrinsic_width_from(
2499        &self,
2500        index: usize,
2501        frame: &CoordinatorFrame<'_>,
2502        height: f32,
2503    ) -> f32 {
2504        let Some(node) = self.nodes.get(index) else {
2505            return frame
2506                .measure_policy
2507                .min_intrinsic_width(frame.measurables, height);
2508        };
2509        let wrapped = CoordinatorLink {
2510            chain: self,
2511            frame,
2512            index: index + 1,
2513        };
2514        let node_borrow = node.node.borrow();
2515        node_borrow.as_layout_node().map_or_else(
2516            || wrapped.min_intrinsic_width(height),
2517            |layout_node| layout_node.min_intrinsic_width(&wrapped, height),
2518        )
2519    }
2520
2521    fn max_intrinsic_width_from(
2522        &self,
2523        index: usize,
2524        frame: &CoordinatorFrame<'_>,
2525        height: f32,
2526    ) -> f32 {
2527        let Some(node) = self.nodes.get(index) else {
2528            return frame
2529                .measure_policy
2530                .max_intrinsic_width(frame.measurables, height);
2531        };
2532        let wrapped = CoordinatorLink {
2533            chain: self,
2534            frame,
2535            index: index + 1,
2536        };
2537        let node_borrow = node.node.borrow();
2538        node_borrow.as_layout_node().map_or_else(
2539            || wrapped.max_intrinsic_width(height),
2540            |layout_node| layout_node.max_intrinsic_width(&wrapped, height),
2541        )
2542    }
2543
2544    fn min_intrinsic_height_from(
2545        &self,
2546        index: usize,
2547        frame: &CoordinatorFrame<'_>,
2548        width: f32,
2549    ) -> f32 {
2550        let Some(node) = self.nodes.get(index) else {
2551            return frame
2552                .measure_policy
2553                .min_intrinsic_height(frame.measurables, width);
2554        };
2555        let wrapped = CoordinatorLink {
2556            chain: self,
2557            frame,
2558            index: index + 1,
2559        };
2560        let node_borrow = node.node.borrow();
2561        node_borrow.as_layout_node().map_or_else(
2562            || wrapped.min_intrinsic_height(width),
2563            |layout_node| layout_node.min_intrinsic_height(&wrapped, width),
2564        )
2565    }
2566
2567    fn max_intrinsic_height_from(
2568        &self,
2569        index: usize,
2570        frame: &CoordinatorFrame<'_>,
2571        width: f32,
2572    ) -> f32 {
2573        let Some(node) = self.nodes.get(index) else {
2574            return frame
2575                .measure_policy
2576                .max_intrinsic_height(frame.measurables, width);
2577        };
2578        let wrapped = CoordinatorLink {
2579            chain: self,
2580            frame,
2581            index: index + 1,
2582        };
2583        let node_borrow = node.node.borrow();
2584        node_borrow.as_layout_node().map_or_else(
2585            || wrapped.max_intrinsic_height(width),
2586            |layout_node| layout_node.max_intrinsic_height(&wrapped, width),
2587        )
2588    }
2589
2590    fn total_content_offset_from(&self, index: usize) -> Point {
2591        self.nodes
2592            .get(index)
2593            .map(|node| node.accumulated_offset.get())
2594            .unwrap_or_default()
2595    }
2596
2597    #[cfg(test)]
2598    fn debug_ptrs(&self) -> Vec<usize> {
2599        self.nodes.iter().map(CoordinatorNode::ptr).collect()
2600    }
2601}
2602
2603#[derive(Default)]
2604pub(crate) struct LayoutRuntimeState {
2605    child_ids: Vec<NodeId>,
2606    child_states: Vec<Rc<LayoutChildMeasureState>>,
2607    child_measurables: Vec<Box<dyn Measurable>>,
2608    coordinator_chain: CoordinatorChain,
2609}
2610
2611impl LayoutRuntimeState {
2612    fn reconcile_child_measurables(&mut self, child_ids: &[NodeId]) {
2613        if self.child_ids == child_ids {
2614            return;
2615        }
2616
2617        let mut previous_ids = std::mem::take(&mut self.child_ids);
2618        let mut previous_states = std::mem::take(&mut self.child_states);
2619        let mut previous_measurables = std::mem::take(&mut self.child_measurables);
2620
2621        self.child_ids.reserve(child_ids.len());
2622        self.child_states.reserve(child_ids.len());
2623        self.child_measurables.reserve(child_ids.len());
2624
2625        for &child_id in child_ids {
2626            if let Some(position) = previous_ids.iter().position(|&id| id == child_id) {
2627                self.child_ids.push(previous_ids.swap_remove(position));
2628                self.child_states
2629                    .push(previous_states.swap_remove(position));
2630                self.child_measurables
2631                    .push(previous_measurables.swap_remove(position));
2632            } else {
2633                let state = LayoutChildMeasureState::new(child_id);
2634                self.child_ids.push(child_id);
2635                self.child_states.push(Rc::clone(&state));
2636                self.child_measurables
2637                    .push(Box::new(LayoutChildMeasurable::new(state)));
2638            }
2639        }
2640    }
2641
2642    fn child_state(&self, index: usize) -> Rc<LayoutChildMeasureState> {
2643        Rc::clone(&self.child_states[index])
2644    }
2645
2646    fn child_measurables(&self) -> &[Box<dyn Measurable>] {
2647        self.child_measurables.as_slice()
2648    }
2649
2650    fn reconcile_coordinator_chain(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2651        self.coordinator_chain.reconcile(layout_node_data);
2652    }
2653
2654    fn coordinator_chain(&self) -> &CoordinatorChain {
2655        &self.coordinator_chain
2656    }
2657
2658    fn clear_frame_bindings(&self) {
2659        for child_state in &self.child_states {
2660            child_state.clear_frame_bindings();
2661        }
2662    }
2663
2664    #[cfg(test)]
2665    pub(crate) fn debug_stats(&self) -> LayoutRuntimeDebugStats {
2666        LayoutRuntimeDebugStats {
2667            child_ids: self.child_ids.clone(),
2668            child_state_ptrs: self
2669                .child_states
2670                .iter()
2671                .map(|state| Rc::as_ptr(state) as *const () as usize)
2672                .collect(),
2673            child_measurable_ptrs: self
2674                .child_measurables
2675                .iter()
2676                .map(|measurable| {
2677                    measurable.as_ref() as *const dyn Measurable as *const () as usize
2678                })
2679                .collect(),
2680            child_measurable_count: self.child_measurables.len(),
2681            coordinator_node_ptrs: self.coordinator_chain.debug_ptrs(),
2682            coordinator_node_count: self.coordinator_chain.nodes.len(),
2683        }
2684    }
2685}
2686
2687#[cfg(test)]
2688#[derive(Debug, Clone, PartialEq, Eq)]
2689pub(crate) struct LayoutRuntimeDebugStats {
2690    pub(crate) child_ids: Vec<NodeId>,
2691    pub(crate) child_state_ptrs: Vec<usize>,
2692    pub(crate) child_measurable_ptrs: Vec<usize>,
2693    pub(crate) child_measurable_count: usize,
2694    pub(crate) coordinator_node_ptrs: Vec<usize>,
2695    pub(crate) coordinator_node_count: usize,
2696}
2697
2698struct LayoutChildMeasureConfig {
2699    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2700    node_id: NodeId,
2701    error: Rc<RefCell<Option<NodeError>>>,
2702    runtime_handle: Option<RuntimeHandle>,
2703    cache: LayoutNodeCacheHandles,
2704    cache_epoch: u64,
2705    force_remeasure: bool,
2706    measure_handle: Option<LayoutMeasureHandle>,
2707    layout_state: Option<Rc<RefCell<LayoutState>>>,
2708}
2709
2710struct LayoutChildMeasureState {
2711    applier: RefCell<Option<Rc<ConcreteApplierHost<MemoryApplier>>>>,
2712    node_id: Cell<NodeId>,
2713    measured: RefCell<Option<Rc<MeasuredNode>>>,
2714    last_position: Cell<Option<Point>>,
2715    error: RefCell<Option<Rc<RefCell<Option<NodeError>>>>>,
2716    runtime_handle: RefCell<Option<RuntimeHandle>>,
2717    cache: RefCell<LayoutNodeCacheHandles>,
2718    cache_epoch: Cell<u64>,
2719    force_remeasure: Cell<bool>,
2720    measure_handle: RefCell<Option<LayoutMeasureHandle>>,
2721    layout_state: RefCell<Option<Rc<RefCell<LayoutState>>>>,
2722}
2723
2724impl LayoutChildMeasureState {
2725    fn new(node_id: NodeId) -> Rc<Self> {
2726        Rc::new(Self {
2727            applier: RefCell::new(None),
2728            node_id: Cell::new(node_id),
2729            measured: RefCell::new(None),
2730            last_position: Cell::new(None),
2731            error: RefCell::new(None),
2732            runtime_handle: RefCell::new(None),
2733            cache: RefCell::new(LayoutNodeCacheHandles::default()),
2734            cache_epoch: Cell::new(0),
2735            force_remeasure: Cell::new(true),
2736            measure_handle: RefCell::new(None),
2737            layout_state: RefCell::new(None),
2738        })
2739    }
2740
2741    fn configure(&self, config: LayoutChildMeasureConfig) {
2742        config.cache.activate(config.cache_epoch);
2743        *self.applier.borrow_mut() = Some(config.applier);
2744        self.node_id.set(config.node_id);
2745        self.measured.borrow_mut().take();
2746        self.last_position.set(None);
2747        *self.error.borrow_mut() = Some(config.error);
2748        *self.runtime_handle.borrow_mut() = config.runtime_handle;
2749        *self.cache.borrow_mut() = config.cache;
2750        self.cache_epoch.set(config.cache_epoch);
2751        self.force_remeasure.set(config.force_remeasure);
2752        *self.measure_handle.borrow_mut() = config.measure_handle;
2753        *self.layout_state.borrow_mut() = config.layout_state;
2754    }
2755
2756    fn clear_frame_bindings(&self) {
2757        self.measured.borrow_mut().take();
2758        *self.applier.borrow_mut() = None;
2759        *self.error.borrow_mut() = None;
2760        *self.runtime_handle.borrow_mut() = None;
2761        *self.measure_handle.borrow_mut() = None;
2762        *self.layout_state.borrow_mut() = None;
2763    }
2764
2765    fn node_id(&self) -> NodeId {
2766        self.node_id.get()
2767    }
2768
2769    fn cache(&self) -> LayoutNodeCacheHandles {
2770        self.cache.borrow().clone()
2771    }
2772
2773    fn applier(&self) -> Option<Rc<ConcreteApplierHost<MemoryApplier>>> {
2774        self.applier.borrow().clone()
2775    }
2776
2777    fn layout_state(&self) -> Option<Rc<RefCell<LayoutState>>> {
2778        self.layout_state.borrow().clone()
2779    }
2780
2781    fn take_measured(&self) -> Option<Rc<MeasuredNode>> {
2782        self.measured.borrow_mut().take()
2783    }
2784
2785    fn last_position(&self) -> Option<Point> {
2786        self.last_position.get()
2787    }
2788
2789    fn set_last_position(&self, position: Point) {
2790        self.last_position.set(Some(position));
2791    }
2792
2793    fn place_retained(&self, position: Point) {
2794        self.set_last_position(position);
2795        if let Some(layout_state) = self.layout_state() {
2796            layout_state.borrow_mut().place(position);
2797            return;
2798        }
2799        let Some(applier) = self.applier() else {
2800            return;
2801        };
2802        let Ok(mut applier) = applier.try_borrow_typed() else {
2803            return;
2804        };
2805        let node_id = self.node_id();
2806        if applier
2807            .with_node::<LayoutNode, _>(node_id, |node| {
2808                node.set_position(position);
2809            })
2810            .is_err()
2811        {
2812            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2813                node.set_position(position);
2814            });
2815        }
2816    }
2817
2818    fn set_measured(&self, measured: Option<Rc<MeasuredNode>>) {
2819        *self.measured.borrow_mut() = measured;
2820    }
2821
2822    fn record_error(&self, err: NodeError) {
2823        let Some(error) = self.error.borrow().clone() else {
2824            return;
2825        };
2826        let mut slot = error.borrow_mut();
2827        if slot.is_none() {
2828            *slot = Some(err);
2829        }
2830    }
2831
2832    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
2833        let node_id = self.node_id();
2834        if let Some(handle) = self.measure_handle.borrow().clone() {
2835            return handle.measure(node_id, constraints);
2836        }
2837        let applier = self.applier().ok_or(NodeError::MissingContext {
2838            id: node_id,
2839            reason: "layout child applier not configured",
2840        })?;
2841        measure_node_with_host(
2842            applier,
2843            self.runtime_handle.borrow().clone(),
2844            node_id,
2845            constraints,
2846            self.cache_epoch.get(),
2847        )
2848    }
2849
2850    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
2851        let cache = self.cache();
2852        cache.activate(self.cache_epoch.get());
2853        if !self.force_remeasure.get()
2854            && let Some(cached) = cache.get_measurement(constraints)
2855        {
2856            return Some(cached);
2857        }
2858
2859        match self.perform_measure(constraints) {
2860            Ok(measured) => {
2861                self.force_remeasure.set(false);
2862                cache.store_measurement(constraints, Rc::clone(&measured));
2863                Some(measured)
2864            }
2865            Err(err) => {
2866                self.record_error(err);
2867                None
2868            }
2869        }
2870    }
2871}
2872
2873struct LayoutChildMeasurable {
2874    state: Rc<LayoutChildMeasureState>,
2875}
2876
2877impl LayoutChildMeasurable {
2878    fn new(state: Rc<LayoutChildMeasureState>) -> Self {
2879        Self { state }
2880    }
2881
2882    fn resolved_parent_data(&self) -> Option<cranpose_ui_layout::ParentData> {
2883        let applier = self.state.applier()?;
2884        let node_id = self.state.node_id();
2885        let Ok(mut applier) = applier.try_borrow_typed() else {
2886            return None;
2887        };
2888
2889        applier
2890            .with_node::<LayoutNode, _>(node_id, |layout_node| {
2891                let props = layout_node.resolved_modifiers().layout_properties();
2892                let weight = props.weight().unwrap_or_default();
2893                cranpose_ui_layout::ParentData {
2894                    weight: weight.weight,
2895                    fill: weight.fill,
2896                    box_alignment: props.box_alignment(),
2897                    row_alignment: props.row_alignment(),
2898                    column_alignment: props.column_alignment(),
2899                }
2900            })
2901            .ok()
2902    }
2903}
2904
2905impl Measurable for LayoutChildMeasurable {
2906    fn measure(&self, constraints: Constraints) -> Placeable {
2907        let state = &self.state;
2908        let cache = state.cache();
2909        cache.activate(state.cache_epoch.get());
2910        let measured_size;
2911        if !state.force_remeasure.get() {
2912            if let Some(cached) = cache.get_measurement(constraints) {
2913                measured_size = cached.size;
2914                state.set_measured(Some(Rc::clone(&cached)));
2915            } else {
2916                match state.perform_measure(constraints) {
2917                    Ok(measured) => {
2918                        state.force_remeasure.set(false);
2919                        measured_size = measured.size;
2920                        cache.store_measurement(constraints, Rc::clone(&measured));
2921                        state.set_measured(Some(measured));
2922                    }
2923                    Err(err) => {
2924                        state.record_error(err);
2925                        state.set_measured(None);
2926                        measured_size = Size {
2927                            width: 0.0,
2928                            height: 0.0,
2929                        };
2930                    }
2931                }
2932            }
2933        } else {
2934            match state.perform_measure(constraints) {
2935                Ok(measured) => {
2936                    state.force_remeasure.set(false);
2937                    measured_size = measured.size;
2938                    cache.store_measurement(constraints, Rc::clone(&measured));
2939                    state.set_measured(Some(measured));
2940                }
2941                Err(err) => {
2942                    state.record_error(err);
2943                    state.set_measured(None);
2944                    measured_size = Size {
2945                        width: 0.0,
2946                        height: 0.0,
2947                    };
2948                }
2949            }
2950        }
2951
2952        if let Some(layout_state) = state.layout_state() {
2953            let mut layout_state = layout_state.borrow_mut();
2954            layout_state.set_size(measured_size);
2955        } else if let Some(applier) = state.applier() {
2956            let Ok(mut applier) = applier.try_borrow_typed() else {
2957                return Placeable::value(
2958                    measured_size.width,
2959                    measured_size.height,
2960                    state.node_id(),
2961                );
2962            };
2963            let _ = applier.with_node::<LayoutNode, _>(state.node_id(), |node| {
2964                node.set_measured_size(measured_size);
2965            });
2966        }
2967
2968        let state = Rc::clone(&self.state);
2969        let node_id = state.node_id();
2970        let size_for_parent = state
2971            .measured
2972            .borrow()
2973            .as_ref()
2974            .map_or(measured_size, |measured| measured.size_for_parent());
2975
2976        let place_fn = Rc::new(move |x: f32, y: f32| {
2977            let internal_offset = state
2978                .measured
2979                .borrow()
2980                .as_ref()
2981                .map(|m| m.offset)
2982                .unwrap_or_default();
2983
2984            state.place_retained(Point {
2985                x: x + internal_offset.x,
2986                y: y + internal_offset.y,
2987            });
2988        });
2989
2990        Placeable::with_place_fn(
2991            size_for_parent.width,
2992            size_for_parent.height,
2993            node_id,
2994            place_fn,
2995        )
2996    }
2997
2998    fn min_intrinsic_width(&self, height: f32) -> f32 {
2999        let kind = IntrinsicKind::MinWidth(height);
3000        let cache = self.state.cache();
3001        cache.activate(self.state.cache_epoch.get());
3002        if !self.state.force_remeasure.get()
3003            && let Some(value) = cache.get_intrinsic(&kind)
3004        {
3005            return value;
3006        }
3007        let constraints = Constraints {
3008            min_width: 0.0,
3009            max_width: f32::INFINITY,
3010            min_height: height,
3011            max_height: height,
3012        };
3013        if let Some(node) = self.state.intrinsic_measure(constraints) {
3014            let value = node.size_for_parent().width;
3015            cache.store_intrinsic(kind, value);
3016            value
3017        } else {
3018            0.0
3019        }
3020    }
3021
3022    fn max_intrinsic_width(&self, height: f32) -> f32 {
3023        let kind = IntrinsicKind::MaxWidth(height);
3024        let cache = self.state.cache();
3025        cache.activate(self.state.cache_epoch.get());
3026        if !self.state.force_remeasure.get()
3027            && let Some(value) = cache.get_intrinsic(&kind)
3028        {
3029            return value;
3030        }
3031        let constraints = Constraints {
3032            min_width: 0.0,
3033            max_width: f32::INFINITY,
3034            min_height: 0.0,
3035            max_height: height,
3036        };
3037        if let Some(node) = self.state.intrinsic_measure(constraints) {
3038            let value = node.size_for_parent().width;
3039            cache.store_intrinsic(kind, value);
3040            value
3041        } else {
3042            0.0
3043        }
3044    }
3045
3046    fn min_intrinsic_height(&self, width: f32) -> f32 {
3047        let kind = IntrinsicKind::MinHeight(width);
3048        let cache = self.state.cache();
3049        cache.activate(self.state.cache_epoch.get());
3050        if !self.state.force_remeasure.get()
3051            && let Some(value) = cache.get_intrinsic(&kind)
3052        {
3053            return value;
3054        }
3055        let constraints = Constraints {
3056            min_width: width,
3057            max_width: width,
3058            min_height: 0.0,
3059            max_height: f32::INFINITY,
3060        };
3061        if let Some(node) = self.state.intrinsic_measure(constraints) {
3062            let value = node.size_for_parent().height;
3063            cache.store_intrinsic(kind, value);
3064            value
3065        } else {
3066            0.0
3067        }
3068    }
3069
3070    fn max_intrinsic_height(&self, width: f32) -> f32 {
3071        let kind = IntrinsicKind::MaxHeight(width);
3072        let cache = self.state.cache();
3073        cache.activate(self.state.cache_epoch.get());
3074        if !self.state.force_remeasure.get()
3075            && let Some(value) = cache.get_intrinsic(&kind)
3076        {
3077            return value;
3078        }
3079        let constraints = Constraints {
3080            min_width: 0.0,
3081            max_width: width,
3082            min_height: 0.0,
3083            max_height: f32::INFINITY,
3084        };
3085        if let Some(node) = self.state.intrinsic_measure(constraints) {
3086            let value = node.size_for_parent().height;
3087            cache.store_intrinsic(kind, value);
3088            value
3089        } else {
3090            0.0
3091        }
3092    }
3093
3094    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
3095        let parent_data = self.resolved_parent_data()?;
3096        if !parent_data.has_weight() {
3097            return None;
3098        }
3099        Some(cranpose_ui_layout::FlexParentData::new(
3100            parent_data.weight,
3101            parent_data.fill,
3102        ))
3103    }
3104
3105    fn parent_data(&self) -> cranpose_ui_layout::ParentData {
3106        self.resolved_parent_data().unwrap_or_default()
3107    }
3108}
3109
3110fn measure_node_with_host(
3111    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
3112    runtime_handle: Option<RuntimeHandle>,
3113    node_id: NodeId,
3114    constraints: Constraints,
3115    epoch: u64,
3116) -> Result<Rc<MeasuredNode>, NodeError> {
3117    let runtime_handle = match runtime_handle {
3118        Some(handle) => Some(handle),
3119        None => applier.borrow_typed().runtime_handle(),
3120    };
3121    let mut builder = LayoutBuilder::new_with_epoch(
3122        applier,
3123        epoch,
3124        Rc::new(RefCell::new(SlotTable::default())),
3125        FrameLayoutArena::default(),
3126    );
3127    builder.set_runtime_handle(runtime_handle);
3128    builder.measure_node(node_id, constraints)
3129}
3130
3131#[derive(Clone)]
3132struct RuntimeNodeMetadata {
3133    modifier: Modifier,
3134    resolved_modifiers: ResolvedModifiers,
3135    modifier_slices: Rc<ModifierNodeSlices>,
3136    semantics: Option<Rc<SemanticsConfiguration>>,
3137    role: SemanticsRole,
3138    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
3139}
3140
3141impl Default for RuntimeNodeMetadata {
3142    fn default() -> Self {
3143        Self {
3144            modifier: Modifier::empty(),
3145            resolved_modifiers: ResolvedModifiers::default(),
3146            modifier_slices: Rc::default(),
3147            semantics: None,
3148            role: SemanticsRole::Unknown,
3149            button_handler: None,
3150        }
3151    }
3152}
3153
3154fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
3155    modifier_slices
3156        .text_content()
3157        .map_or(SemanticsRole::Layout, |text| SemanticsRole::Text {
3158            value: text.to_string(),
3159        })
3160}
3161
3162fn runtime_metadata_for(
3163    applier: &mut MemoryApplier,
3164    node_id: NodeId,
3165) -> Result<RuntimeNodeMetadata, NodeError> {
3166    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
3167        let modifier = layout.modifier.clone();
3168        let resolved_modifiers = layout.resolved_modifiers();
3169        let modifier_slices = layout.modifier_slices_snapshot();
3170        let role = role_from_modifier_slices(&modifier_slices);
3171
3172        RuntimeNodeMetadata {
3173            modifier,
3174            resolved_modifiers,
3175            modifier_slices,
3176            semantics: layout.semantics_configuration().map(Rc::new),
3177            role,
3178            button_handler: None,
3179        }
3180    }) {
3181        return Ok(meta);
3182    }
3183
3184    if let Ok((modifier, resolved_modifiers, modifier_slices, semantics)) =
3185        applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
3186            (
3187                node.modifier(),
3188                node.resolved_modifiers(),
3189                node.modifier_slices_snapshot(),
3190                node.semantics_configuration().map(Rc::new),
3191            )
3192        })
3193    {
3194        return Ok(RuntimeNodeMetadata {
3195            modifier,
3196            resolved_modifiers,
3197            modifier_slices,
3198            semantics,
3199            role: SemanticsRole::Subcompose,
3200            button_handler: None,
3201        });
3202    }
3203    Ok(RuntimeNodeMetadata::default())
3204}
3205
3206fn clear_semantics_dirty_flags(
3207    applier: &mut MemoryApplier,
3208    node: &MeasuredNode,
3209) -> Result<(), NodeError> {
3210    match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3211        layout.clear_needs_semantics();
3212    }) {
3213        Ok(()) => {}
3214        Err(NodeError::Missing { .. }) => {}
3215        Err(NodeError::TypeMismatch { .. }) => {
3216            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3217                subcompose.clear_needs_semantics();
3218            }) {
3219                Ok(()) | Err(NodeError::Missing { .. } | NodeError::TypeMismatch { .. }) => {}
3220                Err(err) => return Err(err),
3221            }
3222        }
3223        Err(err) => return Err(err),
3224    }
3225
3226    for child in &node.children {
3227        clear_semantics_dirty_flags(applier, &child.node)?;
3228    }
3229
3230    Ok(())
3231}
3232
3233fn build_semantics_tree_from_live_nodes(
3234    applier: &mut MemoryApplier,
3235    node: &MeasuredNode,
3236) -> Result<SemanticsTree, NodeError> {
3237    Ok(SemanticsTree::new(build_semantics_node_from_live_nodes(
3238        applier, node,
3239    )?))
3240}
3241
3242fn semantics_node_from_parts(
3243    node_id: NodeId,
3244    node_generation: u32,
3245    mut role: SemanticsRole,
3246    config: Option<SemanticsConfiguration>,
3247    children: Vec<SemanticsNode>,
3248    size: Size,
3249) -> SemanticsNode {
3250    let mut node = SemanticsNode {
3251        node_id,
3252        node_generation,
3253        children,
3254        ..SemanticsNode::default()
3255    };
3256
3257    if let Some(config) = config {
3258        if config.role == Some(SemanticsWidgetRole::Button) {
3259            role = SemanticsRole::Button;
3260        }
3261        if config.is_activatable() {
3262            node.actions.push(SemanticsAction::Click {
3263                handler: SemanticsCallback::new(node_id),
3264            });
3265        }
3266        node.widget_role = config.role;
3267        node.description = config.content_description;
3268        node.state_description = config.state_description;
3269        node.on_click_label = config.on_click_label.or_else(|| {
3270            config
3271                .on_click
3272                .as_ref()
3273                .and_then(|action| (!action.label.is_empty()).then(|| action.label.clone()))
3274        });
3275        node.on_click = config.on_click;
3276        node.on_long_click = config.on_long_click;
3277        node.on_long_click_label = config.on_long_click_label;
3278        node.on_magic_tap = config.on_magic_tap;
3279        node.on_magic_tap_label = config.on_magic_tap_label;
3280        node.input_labels = config.input_labels;
3281        node.language = config.language;
3282        node.selected = config.selected;
3283        node.toggled = config.toggled;
3284        node.enabled = config.enabled;
3285        node.custom_actions = config.custom_actions;
3286        node.canvas_children = config.canvas_children;
3287        node.editable_text = config.is_editable_text;
3288        node.multiline = config.multiline;
3289        node.hidden = config.hidden;
3290        node.is_modal = config.is_modal
3291            && size.width > 0.0
3292            && size.height > 0.0
3293            && size.width.is_finite()
3294            && size.height.is_finite();
3295        node.merge_descendants = config.merge_descendants;
3296        node.selectable_group = config.selectable_group;
3297        node.pane_title = config.pane_title;
3298        node.error = config.error;
3299        node.password = config.password;
3300        node.traversal_index = config.traversal_index;
3301        node.text = config.text;
3302        node.text_selection = config.text_selection;
3303        node.live_region = config.live_region;
3304        node.progress = config.progress;
3305        node.set_progress = config.set_progress;
3306        node.set_text = config.set_text;
3307        node.set_selection = config.set_selection;
3308        node.expand = config.expand;
3309        node.collapse = config.collapse;
3310        node.dismiss = config.dismiss;
3311        node.vertical_scroll = config.vertical_scroll;
3312        node.horizontal_scroll = config.horizontal_scroll;
3313        node.scroll_by = config.scroll_by;
3314        node.scroll_to_index = config.scroll_to_index;
3315        node.collection = config.collection;
3316    }
3317
3318    node.focusable = crate::focus_dispatch::has_focus_target(node_id);
3319    node.focused = node.focusable && crate::focus_dispatch::active_focus_target() == Some(node_id);
3320
3321    node.role = role;
3322    node
3323}
3324
3325fn build_semantics_node_from_live_nodes(
3326    applier: &mut MemoryApplier,
3327    node: &MeasuredNode,
3328) -> Result<SemanticsNode, NodeError> {
3329    let (role, config) = match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3330        let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
3331        let config = layout.semantics_configuration();
3332        layout.clear_needs_semantics();
3333        (role, config)
3334    }) {
3335        Ok(data) => data,
3336        Err(NodeError::TypeMismatch { .. } | NodeError::Missing { .. }) => {
3337            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3338                subcompose.clear_needs_semantics();
3339                (
3340                    SemanticsRole::Subcompose,
3341                    subcompose.semantics_configuration(),
3342                )
3343            }) {
3344                Ok(data) => data,
3345                Err(NodeError::TypeMismatch { .. } | NodeError::Missing { .. }) => {
3346                    (SemanticsRole::Unknown, None)
3347                }
3348                Err(err) => return Err(err),
3349            }
3350        }
3351        Err(err) => return Err(err),
3352    };
3353
3354    let mut children = Vec::with_capacity(node.children.len());
3355    for child in &node.children {
3356        children.push(build_semantics_node_from_live_nodes(applier, &child.node)?);
3357    }
3358
3359    Ok(semantics_node_from_parts(
3360        node.node_id,
3361        applier.node_generation(node.node_id),
3362        role,
3363        config,
3364        children,
3365        node.size,
3366    ))
3367}
3368
3369fn record_semantics_allocation_stats(node: &SemanticsNode, stats: &mut LayoutAllocationDebugStats) {
3370    stats.semantics_node_count += 1;
3371    stats.semantics_action_count += node.actions.len();
3372    stats.semantics_action_capacity += node.actions.capacity();
3373    stats.semantics_child_count += node.children.len();
3374    stats.semantics_child_capacity += node.children.capacity();
3375    stats.semantics_heap_bytes += node.actions.capacity() * size_of::<SemanticsAction>();
3376    stats.semantics_heap_bytes += node.children.capacity() * size_of::<SemanticsNode>();
3377
3378    if let Some(description) = &node.description {
3379        stats.semantics_description_count += 1;
3380        stats.semantics_description_bytes += description.capacity();
3381        stats.semantics_heap_bytes += description.capacity();
3382    }
3383    if let SemanticsRole::Text { value } = &node.role {
3384        stats.semantics_text_role_bytes += value.capacity();
3385        stats.semantics_heap_bytes += value.capacity();
3386    }
3387
3388    for child in &node.children {
3389        record_semantics_allocation_stats(child, stats);
3390    }
3391}
3392
3393fn record_layout_box_allocation_stats(
3394    layout_box: &LayoutBox,
3395    stats: &mut LayoutAllocationDebugStats,
3396) {
3397    stats.layout_box_count += 1;
3398    stats.layout_box_child_count += layout_box.children.len();
3399    stats.layout_box_child_capacity += layout_box.children.capacity();
3400    stats.layout_box_heap_bytes += layout_box.children.capacity() * size_of::<LayoutBox>();
3401    stats.add_modifier_slice(layout_box.node_data.modifier_slices().debug_stats());
3402
3403    for child in &layout_box.children {
3404        record_layout_box_allocation_stats(child, stats);
3405    }
3406}
3407
3408fn build_layout_tree(
3409    applier: &mut MemoryApplier,
3410    node: &MeasuredNode,
3411) -> Result<LayoutTree, NodeError> {
3412    fn place(
3413        applier: &mut MemoryApplier,
3414        node: &MeasuredNode,
3415        origin: Point,
3416        parent_layer_translation: Point,
3417    ) -> Result<LayoutBox, NodeError> {
3418        let top_left = Point {
3419            x: origin.x + node.offset.x,
3420            y: origin.y + node.offset.y,
3421        };
3422        let rect = GeometryRect {
3423            x: top_left.x,
3424            y: top_left.y,
3425            width: node.size.width,
3426            height: node.size.height,
3427        };
3428        let (data, layer_translation) = snapshot_node_data(
3429            applier,
3430            node.node_id,
3431            top_left,
3432            node.size,
3433            parent_layer_translation,
3434        )?;
3435        let mut children = Vec::with_capacity(node.children.len());
3436        for child in &node.children {
3437            if crate::modifier::is_window_root(applier, child.node.node_id) {
3438                continue;
3439            }
3440            let child_origin = Point {
3441                x: top_left.x + child.offset.x,
3442                y: top_left.y + child.offset.y,
3443            };
3444            children.push(place(
3445                applier,
3446                &child.node,
3447                child_origin,
3448                layer_translation,
3449            )?);
3450        }
3451        Ok(LayoutBox {
3452            node_generation: applier.node_generation(node.node_id),
3453            ..LayoutBox::new(node.node_id, rect, node.content_offset, data, children)
3454        })
3455    }
3456
3457    Ok(LayoutTree::new(place(
3458        applier,
3459        node,
3460        Point { x: 0.0, y: 0.0 },
3461        Point { x: 0.0, y: 0.0 },
3462    )?))
3463}
3464
3465fn semantics_role_from_layout_box(layout_box: &LayoutBox) -> SemanticsRole {
3466    match &layout_box.node_data.kind {
3467        LayoutNodeKind::Subcompose => SemanticsRole::Subcompose,
3468        LayoutNodeKind::Spacer => SemanticsRole::Spacer,
3469        LayoutNodeKind::Unknown => SemanticsRole::Unknown,
3470        LayoutNodeKind::Button { .. } => SemanticsRole::Button,
3471        LayoutNodeKind::Layout => layout_box
3472            .node_data
3473            .modifier_slices()
3474            .text_content()
3475            .map_or(SemanticsRole::Layout, |text| SemanticsRole::Text {
3476                value: text.to_string(),
3477            }),
3478    }
3479}
3480
3481fn build_semantics_node_from_layout_box(layout_box: &LayoutBox) -> SemanticsNode {
3482    let children = layout_box
3483        .children
3484        .iter()
3485        .map(build_semantics_node_from_layout_box)
3486        .collect();
3487
3488    semantics_node_from_parts(
3489        layout_box.node_id,
3490        layout_box.node_generation,
3491        semantics_role_from_layout_box(layout_box),
3492        layout_box.node_data.semantics().cloned(),
3493        children,
3494        Size::new(layout_box.rect.width, layout_box.rect.height),
3495    )
3496}
3497
3498fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
3499    match &info.role {
3500        SemanticsRole::Layout => LayoutNodeKind::Layout,
3501        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
3502        SemanticsRole::Text { .. } => LayoutNodeKind::Layout,
3503        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
3504        SemanticsRole::Button => {
3505            let handler = info
3506                .button_handler
3507                .as_ref()
3508                .cloned()
3509                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
3510            LayoutNodeKind::Button { on_click: handler }
3511        }
3512        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
3513    }
3514}
3515
3516fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
3517    let horizontal = padding.horizontal_sum();
3518    let vertical = padding.vertical_sum();
3519    let min_width = (constraints.min_width - horizontal).max(0.0);
3520    let mut max_width = constraints.max_width;
3521    if max_width.is_finite() {
3522        max_width = (max_width - horizontal).max(0.0);
3523    }
3524    let min_height = (constraints.min_height - vertical).max(0.0);
3525    let mut max_height = constraints.max_height;
3526    if max_height.is_finite() {
3527        max_height = (max_height - vertical).max(0.0);
3528    }
3529    normalize_constraints(Constraints {
3530        min_width,
3531        max_width,
3532        min_height,
3533        max_height,
3534    })
3535}
3536
3537#[cfg(test)]
3538pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
3539    match alignment {
3540        HorizontalAlignment::Start => 0.0,
3541        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
3542        HorizontalAlignment::End => (available - child).max(0.0),
3543    }
3544}
3545
3546#[cfg(test)]
3547pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
3548    match alignment {
3549        VerticalAlignment::Top => 0.0,
3550        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
3551        VerticalAlignment::Bottom => (available - child).max(0.0),
3552    }
3553}
3554
3555fn resolve_dimension(
3556    base: f32,
3557    explicit: DimensionConstraint,
3558    min_override: Option<f32>,
3559    max_override: Option<f32>,
3560    min_limit: f32,
3561    max_limit: f32,
3562) -> f32 {
3563    let mut min_bound = min_limit;
3564    if let Some(min_value) = min_override {
3565        min_bound = min_bound.max(min_value);
3566    }
3567
3568    let mut max_bound = if max_limit.is_finite() {
3569        max_limit
3570    } else {
3571        max_override.unwrap_or(max_limit)
3572    };
3573    if let Some(max_value) = max_override {
3574        if max_bound.is_finite() {
3575            max_bound = max_bound.min(max_value);
3576        } else {
3577            max_bound = max_value;
3578        }
3579    }
3580    if max_bound < min_bound {
3581        max_bound = min_bound;
3582    }
3583
3584    let mut size = match explicit {
3585        DimensionConstraint::Points(points) => points,
3586        DimensionConstraint::Fraction(fraction) => {
3587            if max_limit.is_finite() {
3588                max_limit * fraction.clamp(0.0, 1.0)
3589            } else {
3590                base
3591            }
3592        }
3593        DimensionConstraint::Unspecified => base,
3594        DimensionConstraint::Intrinsic(_) => base,
3595    };
3596
3597    size = clamp_dimension(size, min_bound, max_bound);
3598    size = clamp_dimension(size, min_limit, max_limit);
3599    size.max(0.0)
3600}
3601
3602fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
3603    let mut result = value.max(min);
3604    if max.is_finite() {
3605        result = result.min(max);
3606    }
3607    result
3608}
3609
3610fn normalize_constraints(mut constraints: Constraints) -> Constraints {
3611    if constraints.max_width < constraints.min_width {
3612        constraints.max_width = constraints.min_width;
3613    }
3614    if constraints.max_height < constraints.min_height {
3615        constraints.max_height = constraints.min_height;
3616    }
3617    constraints
3618}
3619
3620#[cfg(test)]
3621#[path = "tests/layout_tests.rs"]
3622mod tests;