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