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