Skip to main content

cranpose_ui/layout/
mod.rs

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