Skip to main content

cranpose_ui/layout/
mod.rs

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