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