Skip to main content

cranpose_ui/layout/
mod.rs

1// WIP: Layout system infrastructure - many helper types not yet fully wired up
2
3pub mod coordinator;
4pub mod core;
5pub mod policies;
6
7use cranpose_core::collections::map::Entry;
8use cranpose_core::collections::map::HashMap;
9use std::{
10    cell::RefCell,
11    fmt,
12    rc::Rc,
13    sync::atomic::{AtomicU64, Ordering},
14};
15
16use cranpose_core::{
17    Applier, ApplierHost, Composer, ConcreteApplierHost, MemoryApplier, NodeError, NodeId, Phase,
18    RuntimeHandle, SlotBackend, SlotsHost, SnapshotStateObserver,
19};
20
21use self::coordinator::NodeCoordinator;
22use self::core::Measurable;
23use self::core::Placeable;
24#[cfg(test)]
25use self::core::{HorizontalAlignment, VerticalAlignment};
26use crate::modifier::{
27    collect_semantics_from_modifier, DimensionConstraint, EdgeInsets, Modifier, ModifierNodeSlices,
28    Point, Rect as GeometryRect, ResolvedModifiers, Size,
29};
30
31use crate::subcompose_layout::SubcomposeLayoutNode;
32use crate::widgets::nodes::{IntrinsicKind, LayoutNode, LayoutNodeCacheHandles};
33use cranpose_foundation::InvalidationKind;
34use cranpose_foundation::ModifierNodeContext;
35use cranpose_foundation::{NodeCapabilities, SemanticsConfiguration};
36use cranpose_ui_layout::{Constraints, MeasurePolicy, MeasureResult};
37
38/// Runtime context for modifier nodes during measurement.
39///
40/// Unlike `BasicModifierNodeContext`, this context accumulates invalidations
41/// that can be processed after measurement to set dirty flags on the LayoutNode.
42#[derive(Default)]
43pub(crate) struct LayoutNodeContext {
44    invalidations: Vec<InvalidationKind>,
45    update_requested: bool,
46    active_capabilities: Vec<NodeCapabilities>,
47}
48
49impl LayoutNodeContext {
50    pub(crate) fn new() -> Self {
51        Self::default()
52    }
53
54    pub(crate) fn take_invalidations(&mut self) -> Vec<InvalidationKind> {
55        std::mem::take(&mut self.invalidations)
56    }
57}
58
59impl ModifierNodeContext for LayoutNodeContext {
60    fn invalidate(&mut self, kind: InvalidationKind) {
61        if !self.invalidations.contains(&kind) {
62            self.invalidations.push(kind);
63        }
64    }
65
66    fn request_update(&mut self) {
67        self.update_requested = true;
68    }
69
70    fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
71        self.active_capabilities.push(capabilities);
72    }
73
74    fn pop_active_capabilities(&mut self) {
75        self.active_capabilities.pop();
76    }
77}
78
79static NEXT_CACHE_EPOCH: AtomicU64 = AtomicU64::new(1);
80
81/// Forces all layout caches to be invalidated on the next measure by incrementing the epoch.
82///
83/// # ⚠️ Internal Use Only - NOT Public API
84///
85/// **This function is hidden from public documentation and MUST NOT be called by external code.**
86///
87/// Only `cranpose-app-shell` may call this for rare global events:
88/// - Window/viewport resize
89/// - Global font scale or density changes
90/// - Debug toggles that affect all layout
91///
92/// **This is O(entire app size) - extremely expensive!**
93///
94/// # For Local Changes
95///
96/// **Do NOT use this for scroll, single-node mutations, or any local layout change.**
97/// Instead, use the scoped repass mechanism:
98/// ```text
99/// cranpose_ui::schedule_layout_repass(node_id);
100/// ```
101///
102/// The scoped path bubbles dirty flags without invalidating all caches, giving you O(subtree) instead of O(app).
103#[doc(hidden)]
104pub fn invalidate_all_layout_caches() {
105    NEXT_CACHE_EPOCH.fetch_add(1, Ordering::Relaxed);
106}
107
108/// RAII guard that:
109/// - moves the current MemoryApplier into a ConcreteApplierHost
110/// - holds a shared handle to the SlotBackend used by LayoutBuilder
111/// - on Drop, always:
112///   * restores slots into the host from the shared handle
113///   * moves the original MemoryApplier back into the Composition
114///
115/// This makes `measure_layout` panic/Err-safe wrt both the applier and slots.
116/// The key invariant: guard and builder share the same `Rc<RefCell<SlotBackend>>`,
117/// so the guard never loses access to the authoritative slots even on panic.
118struct ApplierSlotGuard<'a> {
119    /// The `MemoryApplier` inside the Composition::applier that we must restore into.
120    target: &'a mut MemoryApplier,
121    /// Host that owns the original MemoryApplier while layout is running.
122    host: Rc<ConcreteApplierHost<MemoryApplier>>,
123    /// Shared handle to the slot table. Both the guard and the builder hold a clone.
124    /// On Drop, we write whatever is in this handle back into the applier.
125    slots: Rc<RefCell<SlotBackend>>,
126}
127
128impl<'a> ApplierSlotGuard<'a> {
129    /// Creates a new guard:
130    /// - moves the current MemoryApplier out of `target` into a host
131    /// - takes the current slots out of the host and wraps them in a shared handle
132    fn new(target: &'a mut MemoryApplier) -> Self {
133        // Move the original applier into a host; leave `target` with a fresh one
134        let original_applier = std::mem::replace(target, MemoryApplier::new());
135        let host = Rc::new(ConcreteApplierHost::new(original_applier));
136
137        // Take slots from the host into a shared handle
138        let slots = {
139            let mut applier_ref = host.borrow_typed();
140            std::mem::take(applier_ref.slots())
141        };
142        let slots = Rc::new(RefCell::new(slots));
143
144        Self {
145            target,
146            host,
147            slots,
148        }
149    }
150
151    /// Rc to pass into LayoutBuilder::new_with_epoch
152    fn host(&self) -> Rc<ConcreteApplierHost<MemoryApplier>> {
153        Rc::clone(&self.host)
154    }
155
156    /// Returns the shared handle to slots for the builder to use.
157    /// The builder clones this Rc, so both guard and builder share the same slots.
158    fn slots_handle(&self) -> Rc<RefCell<SlotBackend>> {
159        Rc::clone(&self.slots)
160    }
161}
162
163impl Drop for ApplierSlotGuard<'_> {
164    fn drop(&mut self) {
165        // 1) Restore slots into the host's MemoryApplier from the shared handle.
166        // This works correctly whether we're on the success path or panic/error path,
167        // because we always have the shared handle.
168        {
169            let mut applier_ref = self.host.borrow_typed();
170            *applier_ref.slots() = std::mem::take(&mut *self.slots.borrow_mut());
171        }
172
173        // 2) Move the original MemoryApplier (with restored/updated slots) back into `target`
174        {
175            let mut applier_ref = self.host.borrow_typed();
176            let original_applier = std::mem::take(&mut *applier_ref);
177            let _ = std::mem::replace(self.target, original_applier);
178        }
179        // No Rc::try_unwrap in Drop → no "panic during panic" risk.
180    }
181}
182
183/// Result of measuring through the modifier node chain.
184struct ModifierChainMeasurement {
185    result: MeasureResult,
186    /// Content offset for scroll/inner transforms - NOT padding semantics
187    content_offset: Point,
188    /// Node's own offset (from OffsetNode, affects position in parent)
189    offset: Point,
190}
191
192type LayoutModifierNodeData = (
193    usize,
194    Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
195);
196
197struct ScratchVecPool<T> {
198    available: Vec<Vec<T>>,
199}
200
201impl<T> ScratchVecPool<T> {
202    fn acquire(&mut self) -> Vec<T> {
203        self.available.pop().unwrap_or_default()
204    }
205
206    fn release(&mut self, mut values: Vec<T>) {
207        values.clear();
208        self.available.push(values);
209    }
210
211    #[cfg(test)]
212    fn available_count(&self) -> usize {
213        self.available.len()
214    }
215}
216
217impl<T> Default for ScratchVecPool<T> {
218    fn default() -> Self {
219        Self {
220            available: Vec::new(),
221        }
222    }
223}
224
225/// Discrete event callback reference produced during semantics extraction.
226#[derive(Clone, Debug, PartialEq, Eq)]
227pub struct SemanticsCallback {
228    node_id: NodeId,
229}
230
231impl SemanticsCallback {
232    pub fn new(node_id: NodeId) -> Self {
233        Self { node_id }
234    }
235
236    pub fn node_id(&self) -> NodeId {
237        self.node_id
238    }
239}
240
241/// Semantics action exposed to the input system.
242#[derive(Clone, Debug, PartialEq, Eq)]
243pub enum SemanticsAction {
244    Click { handler: SemanticsCallback },
245}
246
247/// Semantic role describing how a node should participate in accessibility and hit testing.
248/// Roles are now derived from SemanticsConfiguration rather than widget types.
249#[derive(Clone, Debug, PartialEq, Eq)]
250pub enum SemanticsRole {
251    /// Generic container or layout node
252    Layout,
253    /// Subcomposition boundary
254    Subcompose,
255    /// Text content (derived from TextNode for backward compatibility)
256    Text { value: String },
257    /// Spacer (non-interactive)
258    Spacer,
259    /// Button (derived from is_button semantics flag)
260    Button,
261    /// Unknown or unspecified role
262    Unknown,
263}
264
265/// A single node within the semantics tree.
266#[derive(Clone, Debug)]
267pub struct SemanticsNode {
268    pub node_id: NodeId,
269    pub role: SemanticsRole,
270    pub actions: Vec<SemanticsAction>,
271    pub children: Vec<SemanticsNode>,
272    pub description: Option<String>,
273}
274
275impl SemanticsNode {
276    fn new(
277        node_id: NodeId,
278        role: SemanticsRole,
279        actions: Vec<SemanticsAction>,
280        children: Vec<SemanticsNode>,
281        description: Option<String>,
282    ) -> Self {
283        Self {
284            node_id,
285            role,
286            actions,
287            children,
288            description,
289        }
290    }
291}
292
293/// Rooted semantics tree extracted after layout.
294#[derive(Clone, Debug)]
295pub struct SemanticsTree {
296    root: SemanticsNode,
297}
298
299impl SemanticsTree {
300    fn new(root: SemanticsNode) -> Self {
301        Self { root }
302    }
303
304    pub fn root(&self) -> &SemanticsNode {
305        &self.root
306    }
307}
308
309/// Caches semantics configurations for layout nodes, similar to Jetpack Compose's SemanticsOwner.
310/// This enables lazy semantics tree construction and efficient invalidation.
311#[derive(Default)]
312pub struct SemanticsOwner {
313    configurations: RefCell<HashMap<NodeId, Option<SemanticsConfiguration>>>,
314}
315
316impl SemanticsOwner {
317    pub fn new() -> Self {
318        Self {
319            configurations: RefCell::new(HashMap::default()),
320        }
321    }
322
323    /// Returns the cached configuration for the given node, computing it if necessary.
324    pub fn get_or_compute(
325        &self,
326        node_id: NodeId,
327        applier: &mut MemoryApplier,
328    ) -> Option<SemanticsConfiguration> {
329        // Check cache first
330        if let Some(cached) = self.configurations.borrow().get(&node_id) {
331            return cached.clone();
332        }
333
334        // Compute and cache
335        let config = compute_semantics_for_node(applier, node_id);
336        self.configurations
337            .borrow_mut()
338            .insert(node_id, config.clone());
339        config
340    }
341}
342
343/// Result of running layout for a Compose tree.
344#[derive(Debug, Clone)]
345pub struct LayoutTree {
346    root: LayoutBox,
347}
348
349impl LayoutTree {
350    pub fn new(root: LayoutBox) -> Self {
351        Self { root }
352    }
353
354    pub fn root(&self) -> &LayoutBox {
355        &self.root
356    }
357
358    pub fn root_mut(&mut self) -> &mut LayoutBox {
359        &mut self.root
360    }
361
362    pub fn into_root(self) -> LayoutBox {
363        self.root
364    }
365}
366
367/// Layout information for a single node.
368#[derive(Debug, Clone)]
369pub struct LayoutBox {
370    pub node_id: NodeId,
371    pub rect: GeometryRect,
372    /// Content offset for scroll/inner transforms (applies to children, NOT this node's position)
373    pub content_offset: Point,
374    pub node_data: LayoutNodeData,
375    pub children: Vec<LayoutBox>,
376}
377
378impl LayoutBox {
379    pub fn new(
380        node_id: NodeId,
381        rect: GeometryRect,
382        content_offset: Point,
383        node_data: LayoutNodeData,
384        children: Vec<LayoutBox>,
385    ) -> Self {
386        Self {
387            node_id,
388            rect,
389            content_offset,
390            node_data,
391            children,
392        }
393    }
394}
395
396/// Snapshot of the data required to render a layout node.
397#[derive(Debug, Clone)]
398pub struct LayoutNodeData {
399    pub modifier: Modifier,
400    pub resolved_modifiers: ResolvedModifiers,
401    pub modifier_slices: Rc<ModifierNodeSlices>,
402    pub kind: LayoutNodeKind,
403}
404
405impl LayoutNodeData {
406    pub fn new(
407        modifier: Modifier,
408        resolved_modifiers: ResolvedModifiers,
409        modifier_slices: Rc<ModifierNodeSlices>,
410        kind: LayoutNodeKind,
411    ) -> Self {
412        Self {
413            modifier,
414            resolved_modifiers,
415            modifier_slices,
416            kind,
417        }
418    }
419
420    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
421        self.resolved_modifiers
422    }
423
424    pub fn modifier_slices(&self) -> &ModifierNodeSlices {
425        &self.modifier_slices
426    }
427}
428
429/// Classification of the node captured inside a [`LayoutBox`].
430///
431/// Note: Text content is no longer represented as a distinct LayoutNodeKind.
432/// Text nodes now use `LayoutNodeKind::Layout` with their content stored in
433/// `modifier_slices.text_content()` via TextModifierNode, following Jetpack
434/// Compose's pattern where text is a modifier node capability.
435#[derive(Clone)]
436pub enum LayoutNodeKind {
437    Layout,
438    Subcompose,
439    Spacer,
440    Button { on_click: Rc<RefCell<dyn FnMut()>> },
441    Unknown,
442}
443
444impl fmt::Debug for LayoutNodeKind {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        match self {
447            LayoutNodeKind::Layout => f.write_str("Layout"),
448            LayoutNodeKind::Subcompose => f.write_str("Subcompose"),
449            LayoutNodeKind::Spacer => f.write_str("Spacer"),
450            LayoutNodeKind::Button { .. } => f.write_str("Button"),
451            LayoutNodeKind::Unknown => f.write_str("Unknown"),
452        }
453    }
454}
455
456/// Extension trait that equips `MemoryApplier` with layout computation.
457pub trait LayoutEngine {
458    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError>;
459}
460
461impl LayoutEngine for MemoryApplier {
462    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError> {
463        let measurements = measure_layout(self, root, max_size)?;
464        Ok(measurements.into_layout_tree())
465    }
466}
467
468/// Result of running the measure pass for a Compose layout tree.
469#[derive(Debug, Clone)]
470pub struct LayoutMeasurements {
471    root: Rc<MeasuredNode>,
472    semantics: Option<SemanticsTree>,
473    layout_tree: LayoutTree,
474}
475
476impl LayoutMeasurements {
477    fn new(
478        root: Rc<MeasuredNode>,
479        semantics: Option<SemanticsTree>,
480        layout_tree: LayoutTree,
481    ) -> Self {
482        Self {
483            root,
484            semantics,
485            layout_tree,
486        }
487    }
488
489    /// Returns the measured size of the root node.
490    pub fn root_size(&self) -> Size {
491        self.root.size
492    }
493
494    pub fn semantics_tree(&self) -> Option<&SemanticsTree> {
495        self.semantics.as_ref()
496    }
497
498    /// Consumes the measurements and produces a [`LayoutTree`].
499    pub fn into_layout_tree(self) -> LayoutTree {
500        self.layout_tree
501    }
502
503    /// Returns a borrowed [`LayoutTree`] for rendering.
504    pub fn layout_tree(&self) -> LayoutTree {
505        self.layout_tree.clone()
506    }
507}
508
509#[derive(Clone, Copy, Debug, PartialEq, Eq)]
510pub struct MeasureLayoutOptions {
511    pub collect_semantics: bool,
512}
513
514impl Default for MeasureLayoutOptions {
515    fn default() -> Self {
516        Self {
517            collect_semantics: true,
518        }
519    }
520}
521
522/// Check if a node or any of its descendants needs measure (selective measure optimization).
523/// This can be used by the app shell to skip layout when the tree is clean.
524///
525/// O(1) check - just looks at root's dirty flag.
526/// Works because all mutation paths bubble dirty flags to root via composer commands.
527///
528/// Returns Result to force caller to handle errors explicitly. No more unwrap_or(true) safety net.
529pub fn tree_needs_layout(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
530    // Just check root - bubbling ensures it's dirty if any descendant is dirty
531    let node = applier.get_mut(root)?;
532    let layout_node =
533        node.as_any_mut()
534            .downcast_mut::<LayoutNode>()
535            .ok_or(NodeError::TypeMismatch {
536                id: root,
537                expected: std::any::type_name::<LayoutNode>(),
538            })?;
539    Ok(layout_node.needs_layout())
540}
541
542/// Test helper: bubbles layout dirty flag to root.
543#[cfg(test)]
544pub(crate) fn bubble_layout_dirty(applier: &mut MemoryApplier, node_id: NodeId) {
545    cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
546}
547
548/// Runs the measure phase for the subtree rooted at `root`.
549pub fn measure_layout(
550    applier: &mut MemoryApplier,
551    root: NodeId,
552    max_size: Size,
553) -> Result<LayoutMeasurements, NodeError> {
554    measure_layout_with_options(applier, root, max_size, MeasureLayoutOptions::default())
555}
556
557pub fn measure_layout_with_options(
558    applier: &mut MemoryApplier,
559    root: NodeId,
560    max_size: Size,
561    options: MeasureLayoutOptions,
562) -> Result<LayoutMeasurements, NodeError> {
563    let constraints = Constraints {
564        min_width: 0.0,
565        max_width: max_size.width,
566        min_height: 0.0,
567        max_height: max_size.height,
568    };
569
570    // Selective measure: only increment epoch if something needs MEASURING (not just layout)
571    // O(1) check - just look at root's dirty flag (bubbling ensures correctness)
572    //
573    // CRITICAL: We check needs_MEASURE, not needs_LAYOUT!
574    // - needs_measure: size may change, caches must be invalidated
575    // - needs_layout: position may change but size is cached (e.g., scroll)
576    //
577    // Scroll operations bubble needs_layout to ancestors, but NOT needs_measure.
578    // Using needs_layout here would wipe ALL caches on every scroll frame, causing
579    // O(N) full remeasurement instead of O(changed nodes).
580    let (needs_remeasure, _needs_semantics, cached_epoch) = match applier
581        .with_node::<LayoutNode, _>(root, |node| {
582            (
583                node.needs_measure(), // CORRECT: check needs_measure, not needs_layout
584                node.needs_semantics(),
585                node.cache_handles().epoch(),
586            )
587        }) {
588        Ok(tuple) => tuple,
589        Err(NodeError::TypeMismatch { .. }) => {
590            let node = applier.get_mut(root)?;
591            // For non-LayoutNode roots, check needs_layout as fallback
592            let measure_dirty = node.needs_layout();
593            let semantics_dirty = node.needs_semantics();
594            (measure_dirty, semantics_dirty, 0)
595        }
596        Err(err) => return Err(err),
597    };
598
599    let epoch = if needs_remeasure {
600        NEXT_CACHE_EPOCH.fetch_add(1, Ordering::Relaxed)
601    } else if cached_epoch != 0 {
602        cached_epoch
603    } else {
604        // Fallback when caller root isn't a LayoutNode (e.g. tests using Spacer directly).
605        NEXT_CACHE_EPOCH.load(Ordering::Relaxed)
606    };
607
608    // Move the current applier into a host and set up a guard that will
609    // ALWAYS restore:
610    // - the MemoryApplier back into `applier`
611    // - the SlotBackend back into that MemoryApplier
612    //
613    // IMPORTANT: Declare the guard *before* the builder so the builder
614    // is dropped first (both on Ok and on unwind).
615    let guard = ApplierSlotGuard::new(applier);
616    let applier_host = guard.host();
617    let slots_handle = guard.slots_handle();
618
619    // Give the builder the shared slots handle - both guard and builder
620    // now share access to the same SlotBackend via Rc<RefCell<_>>.
621    let mut builder =
622        LayoutBuilder::new_with_epoch(Rc::clone(&applier_host), epoch, Rc::clone(&slots_handle));
623
624    // ---- Measurement -------------------------------------------------------
625    // If measurement fails, the guard will restore slots from the shared handle
626    // on drop - this is safe because the handle always contains valid slots.
627
628    let measured = builder.measure_node(root, normalize_constraints(constraints))?;
629
630    // Root node has no parent to place it, so we must explicitly place it at (0,0).
631    // This ensures is_placed=true, allowing the renderer to traverse the tree.
632    // Handle both LayoutNode and SubcomposeLayoutNode as potential roots.
633    if let Ok(mut applier) = applier_host.try_borrow_typed() {
634        if applier
635            .with_node::<LayoutNode, _>(root, |node| {
636                node.set_position(Point::default());
637            })
638            .is_err()
639        {
640            let _ = applier.with_node::<SubcomposeLayoutNode, _>(root, |node| {
641                node.set_position(Point::default());
642            });
643        }
644    }
645
646    // ---- Metadata ----------------------------------------------------------
647    let metadata = {
648        let mut applier_ref = applier_host.borrow_typed();
649        collect_runtime_metadata(&mut applier_ref, &measured)?
650    };
651
652    let semantics = if options.collect_semantics {
653        let semantics_snapshot = {
654            let mut applier_ref = applier_host.borrow_typed();
655            collect_semantics_snapshot(&mut applier_ref, &measured)?
656        };
657
658        Some(SemanticsTree::new(build_semantics_node(
659            &measured,
660            &metadata,
661            &semantics_snapshot,
662        )))
663    } else {
664        None
665    };
666
667    // Drop builder before guard - slots are already in the shared handle.
668    // Guard's Drop will write them back to the applier.
669    drop(builder);
670
671    // DO NOT manually unwrap `applier_host` or replace `applier` here.
672    // `ApplierSlotGuard::drop` will restore everything when this function returns.
673
674    let layout_tree = build_layout_tree_from_metadata(&measured, &metadata);
675
676    Ok(LayoutMeasurements::new(measured, semantics, layout_tree))
677}
678
679struct LayoutBuilder {
680    state: Rc<RefCell<LayoutBuilderState>>,
681}
682
683impl LayoutBuilder {
684    fn new_with_epoch(
685        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
686        epoch: u64,
687        slots: Rc<RefCell<SlotBackend>>,
688    ) -> Self {
689        Self {
690            state: Rc::new(RefCell::new(LayoutBuilderState::new_with_epoch(
691                applier, epoch, slots,
692            ))),
693        }
694    }
695
696    fn measure_node(
697        &mut self,
698        node_id: NodeId,
699        constraints: Constraints,
700    ) -> Result<Rc<MeasuredNode>, NodeError> {
701        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
702    }
703
704    fn set_runtime_handle(&mut self, handle: Option<RuntimeHandle>) {
705        self.state.borrow_mut().runtime_handle = handle;
706    }
707}
708
709struct LayoutBuilderState {
710    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
711    runtime_handle: Option<RuntimeHandle>,
712    /// Shared handle to the slot table. This is shared with ApplierSlotGuard
713    /// to ensure panic-safety: even if we panic, the guard can restore slots.
714    slots: Rc<RefCell<SlotBackend>>,
715    cache_epoch: u64,
716    tmp_measurables: ScratchVecPool<Box<dyn Measurable>>,
717    tmp_records: ScratchVecPool<(NodeId, ChildRecord)>,
718    tmp_child_ids: ScratchVecPool<NodeId>,
719    tmp_layout_node_data: ScratchVecPool<LayoutModifierNodeData>,
720}
721
722impl LayoutBuilderState {
723    fn new_with_epoch(
724        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
725        epoch: u64,
726        slots: Rc<RefCell<SlotBackend>>,
727    ) -> Self {
728        let runtime_handle = applier.borrow_typed().runtime_handle();
729
730        Self {
731            applier,
732            runtime_handle,
733            slots,
734            cache_epoch: epoch,
735            tmp_measurables: ScratchVecPool::default(),
736            tmp_records: ScratchVecPool::default(),
737            tmp_child_ids: ScratchVecPool::default(),
738            tmp_layout_node_data: ScratchVecPool::default(),
739        }
740    }
741
742    fn try_with_applier_result<R>(
743        state_rc: &Rc<RefCell<Self>>,
744        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
745    ) -> Option<Result<R, NodeError>> {
746        let host = {
747            let state = state_rc.borrow();
748            Rc::clone(&state.applier)
749        };
750
751        // Try to borrow - if already borrowed (nested call), return None
752        let Ok(mut applier) = host.try_borrow_typed() else {
753            return None;
754        };
755
756        Some(f(&mut applier))
757    }
758
759    fn with_applier_result<R>(
760        state_rc: &Rc<RefCell<Self>>,
761        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
762    ) -> Result<R, NodeError> {
763        Self::try_with_applier_result(state_rc, f).unwrap_or_else(|| {
764            Err(NodeError::MissingContext {
765                id: NodeId::default(),
766                reason: "applier already borrowed",
767            })
768        })
769    }
770
771    /// Clears the is_placed flag for a node at the start of measurement.
772    /// This ensures nodes that drop out of placement won't render with stale geometry.
773    fn clear_node_placed(state_rc: &Rc<RefCell<Self>>, node_id: NodeId) {
774        let host = {
775            let state = state_rc.borrow();
776            Rc::clone(&state.applier)
777        };
778        let Ok(mut applier) = host.try_borrow_typed() else {
779            return;
780        };
781        // Try LayoutNode first, then SubcomposeLayoutNode
782        if applier
783            .with_node::<LayoutNode, _>(node_id, |node| {
784                node.clear_placed();
785            })
786            .is_err()
787        {
788            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
789                node.clear_placed();
790            });
791        }
792    }
793
794    fn measure_node(
795        state_rc: Rc<RefCell<Self>>,
796        node_id: NodeId,
797        constraints: Constraints,
798    ) -> Result<Rc<MeasuredNode>, NodeError> {
799        // Clear is_placed at the start of measurement.
800        // Nodes that are placed will have is_placed set to true via Placeable::place().
801        // Nodes that drop out of placement (not placed this pass) will remain is_placed=false.
802        Self::clear_node_placed(&state_rc, node_id);
803
804        // Try SubcomposeLayoutNode first
805        if let Some(subcompose) =
806            Self::try_measure_subcompose(Rc::clone(&state_rc), node_id, constraints)?
807        {
808            return Ok(subcompose);
809        }
810
811        // Try LayoutNode (the primary modern path)
812        if let Some(result) = Self::try_with_applier_result(&state_rc, |applier| {
813            match applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
814                LayoutNodeSnapshot::from_layout_node(layout_node)
815            }) {
816                Ok(snapshot) => Ok(Some(snapshot)),
817                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
818                Err(err) => Err(err),
819            }
820        }) {
821            // Applier was available, process the result
822            if let Some(snapshot) = result? {
823                return Self::measure_layout_node(
824                    Rc::clone(&state_rc),
825                    node_id,
826                    snapshot,
827                    constraints,
828                );
829            }
830        }
831        // If applier was busy (None) or snapshot was None, fall through to fallback
832
833        // No alternate fallbacks - all widgets use LayoutNode or SubcomposeLayoutNode
834        // If we reach here, it's an unknown node type (shouldn't happen in normal use)
835        Ok(Rc::new(MeasuredNode::new(
836            node_id,
837            Size::default(),
838            Point { x: 0.0, y: 0.0 },
839            Point::default(), // No content offset for fallback nodes
840            Vec::new(),
841        )))
842    }
843
844    fn try_measure_subcompose(
845        state_rc: Rc<RefCell<Self>>,
846        node_id: NodeId,
847        constraints: Constraints,
848    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
849        let applier_host = {
850            let state = state_rc.borrow();
851            Rc::clone(&state.applier)
852        };
853
854        let (node_handle, resolved_modifiers) = {
855            // Try to borrow - if already borrowed (nested measurement), return None
856            let Ok(mut applier) = applier_host.try_borrow_typed() else {
857                return Ok(None);
858            };
859            let node = match applier.get_mut(node_id) {
860                Ok(node) => node,
861                Err(NodeError::Missing { .. }) => return Ok(None),
862                Err(err) => return Err(err),
863            };
864            let any = node.as_any_mut();
865            if let Some(subcompose) =
866                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
867            {
868                let handle = subcompose.handle();
869                let resolved_modifiers = handle.resolved_modifiers();
870                (handle, resolved_modifiers)
871            } else {
872                return Ok(None);
873            }
874        };
875
876        let runtime_handle = {
877            let mut state = state_rc.borrow_mut();
878            if state.runtime_handle.is_none() {
879                // Try to borrow - if already borrowed, we can't get runtime handle
880                if let Ok(applier) = applier_host.try_borrow_typed() {
881                    state.runtime_handle = applier.runtime_handle();
882                }
883            }
884            state
885                .runtime_handle
886                .clone()
887                .ok_or(NodeError::MissingContext {
888                    id: node_id,
889                    reason: "runtime handle required for subcomposition",
890                })?
891        };
892
893        let props = resolved_modifiers.layout_properties();
894        let padding = resolved_modifiers.padding();
895        let offset = resolved_modifiers.offset();
896        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
897
898        if let DimensionConstraint::Points(width) = props.width() {
899            let constrained_width = width - padding.horizontal_sum();
900            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
901            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
902        }
903        if let DimensionConstraint::Points(height) = props.height() {
904            let constrained_height = height - padding.vertical_sum();
905            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
906            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
907        }
908
909        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
910        let slots_host = slots_guard.host();
911        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
912        let observer = SnapshotStateObserver::new(|callback| callback());
913        let composer = Composer::new(
914            Rc::clone(&slots_host),
915            applier_host_dyn,
916            runtime_handle.clone(),
917            observer,
918            Some(node_id),
919        );
920        composer.enter_phase(Phase::Measure);
921
922        let state_rc_clone = Rc::clone(&state_rc);
923        let measure_error: Rc<RefCell<Option<NodeError>>> = Rc::new(RefCell::new(None));
924        let error_for_measurer = Rc::clone(&measure_error);
925        let measurer = Box::new(
926            move |child_id: NodeId, child_constraints: Constraints| -> Size {
927                match Self::measure_node(Rc::clone(&state_rc_clone), child_id, child_constraints) {
928                    Ok(measured) => measured.size,
929                    Err(err) => {
930                        let mut slot = error_for_measurer.borrow_mut();
931                        if slot.is_none() {
932                            *slot = Some(err);
933                        }
934                        Size::default()
935                    }
936                }
937            },
938        );
939
940        let measure_result = node_handle.measure(
941            &composer,
942            node_id,
943            inner_constraints,
944            measurer,
945            Rc::clone(&measure_error),
946        )?;
947
948        slots_guard.restore(slots_host.take());
949
950        if let Some(err) = measure_error.borrow_mut().take() {
951            return Err(err);
952        }
953
954        // NOTE: Children are now managed by the composer via insert_child commands
955        // (from parent_stack initialization with root). set_active_children is no longer used.
956
957        let mut width = measure_result.size.width + padding.horizontal_sum();
958        let mut height = measure_result.size.height + padding.vertical_sum();
959
960        width = resolve_dimension(
961            width,
962            props.width(),
963            props.min_width(),
964            props.max_width(),
965            constraints.min_width,
966            constraints.max_width,
967        );
968        height = resolve_dimension(
969            height,
970            props.height(),
971            props.min_height(),
972            props.max_height(),
973            constraints.min_height,
974            constraints.max_height,
975        );
976
977        let mut children = Vec::with_capacity(measure_result.placements.len());
978
979        // Update the SubcomposeLayoutNode's size (position will be set by parent's placement)
980        if let Ok(mut applier) = applier_host.try_borrow_typed() {
981            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
982                parent_node.set_measured_size(Size { width, height });
983            });
984        }
985
986        for placement in measure_result.placements {
987            let child =
988                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?;
989            let position = Point {
990                x: padding.left + placement.x,
991                y: padding.top + placement.y,
992            };
993
994            // Critical: Update the child LayoutNode's retained state.
995            // Standard layouts do this via Placeable::place(), but SubcomposeLayout logic
996            // bypasses Placeables and returns raw Placements.
997            if let Ok(mut applier) = applier_host.try_borrow_typed() {
998                let _ = applier.with_node::<LayoutNode, _>(placement.node_id, |node| {
999                    node.set_position(position);
1000                });
1001            }
1002
1003            children.push(MeasuredChild {
1004                node: child,
1005                offset: position,
1006            });
1007        }
1008
1009        // Update the SubcomposeLayoutNode's active children for rendering
1010        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1011
1012        Ok(Some(Rc::new(MeasuredNode::new(
1013            node_id,
1014            Size { width, height },
1015            offset,
1016            Point::default(), // Subcompose nodes: content_offset handled by child layout
1017            children,
1018        ))))
1019    }
1020    /// Measures through the layout modifier coordinator chain using reconciled modifier nodes.
1021    /// Iterates through LayoutModifierNode instances from the ModifierNodeChain and calls
1022    /// their measure() methods, mirroring Jetpack Compose's LayoutModifierNodeCoordinator pattern.
1023    ///
1024    /// Always succeeds, building a coordinator chain (possibly just InnerCoordinator) to measure.
1025    ///
1026    fn measure_through_modifier_chain(
1027        state_rc: &Rc<RefCell<Self>>,
1028        node_id: NodeId,
1029        measurables: &[Box<dyn Measurable>],
1030        measure_policy: &Rc<dyn MeasurePolicy>,
1031        constraints: Constraints,
1032        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1033    ) -> ModifierChainMeasurement {
1034        use cranpose_foundation::NodeCapabilities;
1035
1036        // Collect layout node information from the modifier chain
1037        layout_node_data.clear();
1038        let mut offset = Point::default();
1039
1040        {
1041            let state = state_rc.borrow();
1042            let mut applier = state.applier.borrow_typed();
1043
1044            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1045                let chain_handle = layout_node.modifier_chain();
1046
1047                if !chain_handle.has_layout_nodes() {
1048                    return;
1049                }
1050
1051                // Collect indices and node Rc clones for layout modifier nodes
1052                chain_handle.chain().for_each_forward_matching(
1053                    NodeCapabilities::LAYOUT,
1054                    |node_ref| {
1055                        if let Some(index) = node_ref.entry_index() {
1056                            // Get the Rc clone for this node
1057                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1058                                layout_node_data.push((index, Rc::clone(&node_rc)));
1059                            }
1060
1061                            // Extract offset from OffsetNode for the node's own position
1062                            // The coordinator chain handles placement_offset (for children),
1063                            // but the node's offset affects where IT is positioned in the parent
1064                            node_ref.with_node(|node| {
1065                                if let Some(offset_node) =
1066                                    node.as_any()
1067                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1068                                {
1069                                    let delta = offset_node.offset();
1070                                    offset.x += delta.x;
1071                                    offset.y += delta.y;
1072                                }
1073                            });
1074                        }
1075                    },
1076                );
1077            });
1078        }
1079
1080        // Fast path: if there are no layout modifiers, measure directly without coordinator chain.
1081        // This saves 3 allocations (shared_context, policy_result, InnerCoordinator box).
1082        if layout_node_data.is_empty() {
1083            let result = measure_policy.measure(measurables, constraints);
1084            let final_size = result.size;
1085            let placements = result.placements;
1086
1087            return ModifierChainMeasurement {
1088                result: MeasureResult {
1089                    size: final_size,
1090                    placements,
1091                },
1092                content_offset: Point::default(),
1093                offset,
1094            };
1095        }
1096
1097        // Slow path: build coordinator chain for layout modifiers.
1098        // Popping from the end preserves the "rightmost modifier measures first" order
1099        // without allocating or cloning the collected node list.
1100        // Create a shared context for this measurement pass to track invalidations
1101        let shared_context = Rc::new(RefCell::new(LayoutNodeContext::new()));
1102
1103        // Create the inner coordinator that wraps the measure policy
1104        let policy_result = Rc::new(RefCell::new(None));
1105        let inner_coordinator: Box<dyn NodeCoordinator + '_> =
1106            Box::new(coordinator::InnerCoordinator::new(
1107                Rc::clone(measure_policy),
1108                measurables,
1109                Rc::clone(&policy_result),
1110            ));
1111
1112        // Wrap each layout modifier node in a coordinator, building the chain
1113        let mut current_coordinator = inner_coordinator;
1114        while let Some((_, node_rc)) = layout_node_data.pop() {
1115            current_coordinator = Box::new(coordinator::LayoutModifierCoordinator::new(
1116                node_rc,
1117                current_coordinator,
1118                Rc::clone(&shared_context),
1119            ));
1120        }
1121
1122        // Measure through the complete coordinator chain
1123        let placeable = current_coordinator.measure(constraints);
1124        let final_size = Size {
1125            width: placeable.width(),
1126            height: placeable.height(),
1127        };
1128
1129        // Get accumulated content offset from the placeable (computed during measure)
1130        let content_offset = placeable.content_offset();
1131        let all_placement_offset = Point {
1132            x: content_offset.0,
1133            y: content_offset.1,
1134        };
1135
1136        // The content_offset for scroll/inner transforms is the accumulated placement offset
1137        // MINUS the node's own offset (which affects its position in the parent, not content position).
1138        // This properly separates: node position (offset) vs inner content position (content_offset).
1139        let content_offset = Point {
1140            x: all_placement_offset.x - offset.x,
1141            y: all_placement_offset.y - offset.y,
1142        };
1143
1144        // offset was already extracted from OffsetNode above
1145
1146        let placements = policy_result
1147            .borrow_mut()
1148            .take()
1149            .map(|result| result.placements)
1150            .unwrap_or_default();
1151
1152        // Process any invalidations requested during measurement
1153        let invalidations = shared_context.borrow_mut().take_invalidations();
1154        if !invalidations.is_empty() {
1155            // Mark the LayoutNode as needing the appropriate passes
1156            Self::with_applier_result(state_rc, |applier| {
1157                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1158                    for kind in invalidations {
1159                        match kind {
1160                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1161                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1162                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1163                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1164                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1165                        }
1166                    }
1167                })
1168            })
1169            .ok();
1170        }
1171
1172        ModifierChainMeasurement {
1173            result: MeasureResult {
1174                size: final_size,
1175                placements,
1176            },
1177            content_offset,
1178            offset,
1179        }
1180    }
1181
1182    fn measure_layout_node(
1183        state_rc: Rc<RefCell<Self>>,
1184        node_id: NodeId,
1185        snapshot: LayoutNodeSnapshot,
1186        constraints: Constraints,
1187    ) -> Result<Rc<MeasuredNode>, NodeError> {
1188        let cache_epoch = {
1189            let state = state_rc.borrow();
1190            state.cache_epoch
1191        };
1192        let LayoutNodeSnapshot {
1193            resolved_modifiers,
1194            measure_policy,
1195            cache,
1196            needs_measure,
1197        } = snapshot;
1198        cache.activate(cache_epoch);
1199        let layout_props = resolved_modifiers.layout_properties();
1200
1201        if needs_measure {
1202            // Node has needs_measure=true
1203        }
1204
1205        // Only check cache if not marked as needing measure.
1206        // When needs_measure=true, we MUST re-run measure() even if constraints match,
1207        // because something else changed (e.g., scroll offset, modifier state).
1208        if !needs_measure {
1209            // Check cache for current constraints
1210            if let Some(cached) = cache.get_measurement(constraints) {
1211                // Clear dirty flag after successful cache hit
1212                Self::with_applier_result(&state_rc, |applier| {
1213                    applier.with_node::<LayoutNode, _>(node_id, |node| {
1214                        node.clear_needs_measure();
1215                        node.clear_needs_layout();
1216                    })
1217                })
1218                .ok();
1219                return Ok(cached);
1220            }
1221        }
1222
1223        let (runtime_handle, applier_host) = {
1224            let state = state_rc.borrow();
1225            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1226        };
1227
1228        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1229        let error = Rc::new(RefCell::new(None));
1230        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1231        let (measurables, records, child_ids, layout_node_data) = pools.parts();
1232
1233        applier_host
1234            .borrow_typed()
1235            .with_node::<LayoutNode, _>(node_id, |node| {
1236                child_ids.extend_from_slice(&node.children);
1237            })?;
1238
1239        for &child_id in child_ids.iter() {
1240            let measured = Rc::new(RefCell::new(None));
1241            let position = Rc::new(RefCell::new(None));
1242
1243            let data = {
1244                let mut applier = applier_host.borrow_typed();
1245                match applier.with_node::<LayoutNode, _>(child_id, |n| {
1246                    (n.cache_handles(), n.layout_state_handle())
1247                }) {
1248                    Ok((cache, state)) => Some((cache, Some(state))),
1249                    Err(NodeError::TypeMismatch { .. }) => {
1250                        Some((LayoutNodeCacheHandles::default(), None))
1251                    }
1252                    Err(NodeError::Missing { .. }) => None,
1253                    Err(err) => return Err(err),
1254                }
1255            };
1256
1257            let Some((cache_handles, layout_state)) = data else {
1258                continue;
1259            };
1260
1261            cache_handles.activate(cache_epoch);
1262
1263            records.push((
1264                child_id,
1265                ChildRecord {
1266                    measured: Rc::clone(&measured),
1267                    last_position: Rc::clone(&position),
1268                },
1269            ));
1270            measurables.push(Box::new(LayoutChildMeasurable::new(
1271                Rc::clone(&applier_host),
1272                child_id,
1273                measured,
1274                position,
1275                Rc::clone(&error),
1276                runtime_handle.clone(),
1277                cache_handles,
1278                cache_epoch,
1279                Some(measure_handle.clone()),
1280                layout_state,
1281            )));
1282        }
1283
1284        // Try to measure through the modifier node chain first.
1285        // If a node participates in weight-based sizing, never loosen constraints;
1286        // otherwise, use unbounded max on unspecified dimensions to support wrap-content
1287        // behavior without fill modifiers forcing parent size.
1288        let has_weight = layout_props
1289            .weight()
1290            .map(|weight| weight.weight > 0.0)
1291            .unwrap_or(false);
1292        let chain_constraints = if has_weight {
1293            constraints
1294        } else {
1295            Constraints {
1296                min_width: constraints.min_width,
1297                max_width: if matches!(layout_props.width(), DimensionConstraint::Unspecified) {
1298                    f32::INFINITY
1299                } else {
1300                    constraints.max_width
1301                },
1302                min_height: constraints.min_height,
1303                max_height: if matches!(layout_props.height(), DimensionConstraint::Unspecified) {
1304                    f32::INFINITY
1305                } else {
1306                    constraints.max_height
1307                },
1308            }
1309        };
1310
1311        let mut modifier_chain_result = Self::measure_through_modifier_chain(
1312            &state_rc,
1313            node_id,
1314            measurables.as_slice(),
1315            &measure_policy,
1316            chain_constraints,
1317            layout_node_data,
1318        );
1319
1320        if (chain_constraints.max_width != constraints.max_width
1321            || chain_constraints.max_height != constraints.max_height)
1322            && ((constraints.max_width.is_finite()
1323                && modifier_chain_result.result.size.width > constraints.max_width)
1324                || (constraints.max_height.is_finite()
1325                    && modifier_chain_result.result.size.height > constraints.max_height))
1326        {
1327            modifier_chain_result = Self::measure_through_modifier_chain(
1328                &state_rc,
1329                node_id,
1330                measurables.as_slice(),
1331                &measure_policy,
1332                constraints,
1333                layout_node_data,
1334            );
1335        }
1336
1337        // Modifier chain always succeeds - use the node-driven measurement.
1338        let (width, height, policy_result, content_offset, offset) = {
1339            let result = modifier_chain_result;
1340            // The size is already correct from the modifier chain (modifiers like SizeNode
1341            // have already enforced their constraints), so we use it directly.
1342            if let Some(err) = error.borrow_mut().take() {
1343                return Err(err);
1344            }
1345
1346            (
1347                result.result.size.width,
1348                result.result.size.height,
1349                result.result,
1350                result.content_offset,
1351                result.offset,
1352            )
1353        };
1354
1355        let mut measured_children = Vec::with_capacity(records.len());
1356        for (child_id, record) in records.iter() {
1357            if let Some(measured) = record.measured.borrow_mut().take() {
1358                let base_position = policy_result
1359                    .placements
1360                    .iter()
1361                    .find(|placement| placement.node_id == *child_id)
1362                    .map(|placement| Point {
1363                        x: placement.x,
1364                        y: placement.y,
1365                    })
1366                    .or_else(|| record.last_position.borrow().as_ref().copied())
1367                    .unwrap_or(Point { x: 0.0, y: 0.0 });
1368                // Apply content_offset (from scroll/transforms) to child positioning
1369                let position = Point {
1370                    x: content_offset.x + base_position.x,
1371                    y: content_offset.y + base_position.y,
1372                };
1373                measured_children.push(MeasuredChild {
1374                    node: measured,
1375                    offset: position,
1376                });
1377            }
1378        }
1379
1380        let measured = Rc::new(MeasuredNode::new(
1381            node_id,
1382            Size { width, height },
1383            offset,
1384            content_offset,
1385            measured_children,
1386        ));
1387
1388        cache.store_measurement(constraints, Rc::clone(&measured));
1389
1390        // Clear dirty flags and update derived state
1391        Self::with_applier_result(&state_rc, |applier| {
1392            applier.with_node::<LayoutNode, _>(node_id, |node| {
1393                node.clear_needs_measure();
1394                node.clear_needs_layout();
1395                node.set_measured_size(Size { width, height });
1396                node.set_content_offset(content_offset);
1397            })
1398        })
1399        .ok();
1400
1401        Ok(measured)
1402    }
1403}
1404
1405/// Snapshot of a LayoutNode's data for measuring.
1406/// This is a temporary copy used during the measure phase, not a live node.
1407///
1408/// Note: We capture `needs_measure` here because it's checked during measure to enable
1409/// selective measure optimization at the individual node level. Even if the tree is partially
1410/// dirty (some nodes changed), clean nodes can skip measure and use cached results.
1411struct LayoutNodeSnapshot {
1412    resolved_modifiers: ResolvedModifiers,
1413    measure_policy: Rc<dyn MeasurePolicy>,
1414    cache: LayoutNodeCacheHandles,
1415    /// Whether this specific node needs to be measured (vs using cached measurement)
1416    needs_measure: bool,
1417}
1418
1419impl LayoutNodeSnapshot {
1420    fn from_layout_node(node: &LayoutNode) -> Self {
1421        Self {
1422            resolved_modifiers: node.resolved_modifiers(),
1423            measure_policy: Rc::clone(&node.measure_policy),
1424            cache: node.cache_handles(),
1425            needs_measure: node.needs_measure(),
1426        }
1427    }
1428}
1429
1430// Helper types for accessing subsets of LayoutBuilderState
1431struct VecPools {
1432    state: Rc<RefCell<LayoutBuilderState>>,
1433    measurables: Option<Vec<Box<dyn Measurable>>>,
1434    records: Option<Vec<(NodeId, ChildRecord)>>,
1435    child_ids: Option<Vec<NodeId>>,
1436    layout_node_data: Option<Vec<LayoutModifierNodeData>>,
1437}
1438
1439impl VecPools {
1440    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1441        let (measurables, records, child_ids, layout_node_data) = {
1442            let mut state_mut = state.borrow_mut();
1443            (
1444                state_mut.tmp_measurables.acquire(),
1445                state_mut.tmp_records.acquire(),
1446                state_mut.tmp_child_ids.acquire(),
1447                state_mut.tmp_layout_node_data.acquire(),
1448            )
1449        };
1450        Self {
1451            state,
1452            measurables: Some(measurables),
1453            records: Some(records),
1454            child_ids: Some(child_ids),
1455            layout_node_data: Some(layout_node_data),
1456        }
1457    }
1458
1459    #[allow(clippy::type_complexity)] // Returns internal Vec references for layout operations
1460    fn parts(
1461        &mut self,
1462    ) -> (
1463        &mut Vec<Box<dyn Measurable>>,
1464        &mut Vec<(NodeId, ChildRecord)>,
1465        &mut Vec<NodeId>,
1466        &mut Vec<LayoutModifierNodeData>,
1467    ) {
1468        let measurables = self
1469            .measurables
1470            .as_mut()
1471            .expect("measurables already returned");
1472        let records = self.records.as_mut().expect("records already returned");
1473        let child_ids = self.child_ids.as_mut().expect("child_ids already returned");
1474        let layout_node_data = self
1475            .layout_node_data
1476            .as_mut()
1477            .expect("layout_node_data already returned");
1478        (measurables, records, child_ids, layout_node_data)
1479    }
1480}
1481
1482impl Drop for VecPools {
1483    fn drop(&mut self) {
1484        let mut state = self.state.borrow_mut();
1485        if let Some(measurables) = self.measurables.take() {
1486            state.tmp_measurables.release(measurables);
1487        }
1488        if let Some(records) = self.records.take() {
1489            state.tmp_records.release(records);
1490        }
1491        if let Some(child_ids) = self.child_ids.take() {
1492            state.tmp_child_ids.release(child_ids);
1493        }
1494        if let Some(layout_node_data) = self.layout_node_data.take() {
1495            state.tmp_layout_node_data.release(layout_node_data);
1496        }
1497    }
1498}
1499
1500struct SlotsGuard {
1501    state: Rc<RefCell<LayoutBuilderState>>,
1502    slots: Option<SlotBackend>,
1503}
1504
1505impl SlotsGuard {
1506    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1507        let slots = {
1508            let state_ref = state.borrow();
1509            let mut slots_ref = state_ref.slots.borrow_mut();
1510            std::mem::take(&mut *slots_ref)
1511        };
1512        Self {
1513            state,
1514            slots: Some(slots),
1515        }
1516    }
1517
1518    fn host(&mut self) -> Rc<SlotsHost> {
1519        let slots = self.slots.take().unwrap_or_default();
1520        Rc::new(SlotsHost::new(slots))
1521    }
1522
1523    fn restore(&mut self, slots: SlotBackend) {
1524        debug_assert!(self.slots.is_none());
1525        self.slots = Some(slots);
1526    }
1527}
1528
1529impl Drop for SlotsGuard {
1530    fn drop(&mut self) {
1531        if let Some(slots) = self.slots.take() {
1532            let state_ref = self.state.borrow();
1533            *state_ref.slots.borrow_mut() = slots;
1534        }
1535    }
1536}
1537
1538#[derive(Clone)]
1539struct LayoutMeasureHandle {
1540    state: Rc<RefCell<LayoutBuilderState>>,
1541}
1542
1543impl LayoutMeasureHandle {
1544    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1545        Self { state }
1546    }
1547
1548    fn measure(
1549        &self,
1550        node_id: NodeId,
1551        constraints: Constraints,
1552    ) -> Result<Rc<MeasuredNode>, NodeError> {
1553        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
1554    }
1555}
1556
1557#[derive(Debug, Clone)]
1558pub(crate) struct MeasuredNode {
1559    node_id: NodeId,
1560    size: Size,
1561    /// Node's position offset relative to parent (from OffsetNode etc.)
1562    offset: Point,
1563    /// Content offset for scroll/inner transforms (NOT node position)
1564    content_offset: Point,
1565    children: Vec<MeasuredChild>,
1566}
1567
1568impl MeasuredNode {
1569    fn new(
1570        node_id: NodeId,
1571        size: Size,
1572        offset: Point,
1573        content_offset: Point,
1574        children: Vec<MeasuredChild>,
1575    ) -> Self {
1576        Self {
1577            node_id,
1578            size,
1579            offset,
1580            content_offset,
1581            children,
1582        }
1583    }
1584}
1585
1586#[derive(Debug, Clone)]
1587struct MeasuredChild {
1588    node: Rc<MeasuredNode>,
1589    offset: Point,
1590}
1591
1592struct ChildRecord {
1593    measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1594    last_position: Rc<RefCell<Option<Point>>>,
1595}
1596
1597struct LayoutChildMeasurable {
1598    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1599    node_id: NodeId,
1600    measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1601    last_position: Rc<RefCell<Option<Point>>>,
1602    error: Rc<RefCell<Option<NodeError>>>,
1603    runtime_handle: Option<RuntimeHandle>,
1604    cache: LayoutNodeCacheHandles,
1605    cache_epoch: u64,
1606    measure_handle: Option<LayoutMeasureHandle>,
1607    layout_state: Option<Rc<RefCell<crate::widgets::nodes::layout_node::LayoutState>>>,
1608}
1609
1610impl LayoutChildMeasurable {
1611    #[allow(clippy::too_many_arguments)] // Constructor needs all layout state for child measurement
1612    fn new(
1613        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1614        node_id: NodeId,
1615        measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1616        last_position: Rc<RefCell<Option<Point>>>,
1617        error: Rc<RefCell<Option<NodeError>>>,
1618        runtime_handle: Option<RuntimeHandle>,
1619        cache: LayoutNodeCacheHandles,
1620        cache_epoch: u64,
1621        measure_handle: Option<LayoutMeasureHandle>,
1622        layout_state: Option<Rc<RefCell<crate::widgets::nodes::layout_node::LayoutState>>>,
1623    ) -> Self {
1624        cache.activate(cache_epoch);
1625        Self {
1626            applier,
1627            node_id,
1628            measured,
1629            last_position,
1630            error,
1631            runtime_handle,
1632            cache,
1633            cache_epoch,
1634            measure_handle,
1635            layout_state,
1636        }
1637    }
1638
1639    fn record_error(&self, err: NodeError) {
1640        let mut slot = self.error.borrow_mut();
1641        if slot.is_none() {
1642            *slot = Some(err);
1643        }
1644    }
1645
1646    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
1647        if let Some(handle) = &self.measure_handle {
1648            handle.measure(self.node_id, constraints)
1649        } else {
1650            measure_node_with_host(
1651                Rc::clone(&self.applier),
1652                self.runtime_handle.clone(),
1653                self.node_id,
1654                constraints,
1655                self.cache_epoch,
1656            )
1657        }
1658    }
1659
1660    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
1661        self.cache.activate(self.cache_epoch);
1662        if let Some(cached) = self.cache.get_measurement(constraints) {
1663            return Some(cached);
1664        }
1665
1666        match self.perform_measure(constraints) {
1667            Ok(measured) => {
1668                self.cache
1669                    .store_measurement(constraints, Rc::clone(&measured));
1670                Some(measured)
1671            }
1672            Err(err) => {
1673                self.record_error(err);
1674                None
1675            }
1676        }
1677    }
1678}
1679
1680impl Measurable for LayoutChildMeasurable {
1681    fn measure(&self, constraints: Constraints) -> Placeable {
1682        self.cache.activate(self.cache_epoch);
1683        let measured_size;
1684        if let Some(cached) = self.cache.get_measurement(constraints) {
1685            measured_size = cached.size;
1686            *self.measured.borrow_mut() = Some(Rc::clone(&cached));
1687        } else {
1688            match self.perform_measure(constraints) {
1689                Ok(measured) => {
1690                    measured_size = measured.size;
1691                    self.cache
1692                        .store_measurement(constraints, Rc::clone(&measured));
1693                    *self.measured.borrow_mut() = Some(measured);
1694                }
1695                Err(err) => {
1696                    self.record_error(err);
1697                    self.measured.borrow_mut().take();
1698                    measured_size = Size {
1699                        width: 0.0,
1700                        height: 0.0,
1701                    };
1702                }
1703            }
1704        }
1705
1706        // Update retained LayoutNode state with measured size (new architecture).
1707        // PRIORITIZE direct handle to avoid Applier borrow conflicts during layout!
1708        if let Some(state) = &self.layout_state {
1709            let mut state = state.borrow_mut();
1710            state.size = measured_size;
1711            state.measurement_constraints = constraints;
1712        } else if let Ok(mut applier) = self.applier.try_borrow_typed() {
1713            let _ = applier.with_node::<LayoutNode, _>(self.node_id, |node| {
1714                node.set_measured_size(measured_size);
1715                node.set_measurement_constraints(constraints);
1716            });
1717        }
1718
1719        // Build the place closure that captures all state needed for placement
1720        let applier = Rc::clone(&self.applier);
1721        let node_id = self.node_id;
1722        let measured = Rc::clone(&self.measured);
1723        let last_position = Rc::clone(&self.last_position);
1724        let layout_state = self.layout_state.clone();
1725
1726        let place_fn = Rc::new(move |x: f32, y: f32| {
1727            // Retrieve the node's own offset (from modifiers like offset(), padding(), etc.)
1728            let internal_offset = measured
1729                .borrow()
1730                .as_ref()
1731                .map(|m| m.offset)
1732                .unwrap_or_default();
1733
1734            let position = Point {
1735                x: x + internal_offset.x,
1736                y: y + internal_offset.y,
1737            };
1738            *last_position.borrow_mut() = Some(position);
1739
1740            // Update retained LayoutNode state
1741            if let Some(state) = &layout_state {
1742                let mut state = state.borrow_mut();
1743                state.position = position;
1744                state.is_placed = true;
1745            } else if let Ok(mut applier) = applier.try_borrow_typed() {
1746                if applier
1747                    .with_node::<LayoutNode, _>(node_id, |node| {
1748                        node.set_position(position);
1749                    })
1750                    .is_err()
1751                {
1752                    let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1753                        node.set_position(position);
1754                    });
1755                }
1756            }
1757        });
1758
1759        Placeable::with_place_fn(
1760            measured_size.width,
1761            measured_size.height,
1762            self.node_id,
1763            place_fn,
1764        )
1765    }
1766
1767    fn min_intrinsic_width(&self, height: f32) -> f32 {
1768        let kind = IntrinsicKind::MinWidth(height);
1769        self.cache.activate(self.cache_epoch);
1770        if let Some(value) = self.cache.get_intrinsic(&kind) {
1771            return value;
1772        }
1773        let constraints = Constraints {
1774            min_width: 0.0,
1775            max_width: f32::INFINITY,
1776            min_height: height,
1777            max_height: height,
1778        };
1779        if let Some(node) = self.intrinsic_measure(constraints) {
1780            let value = node.size.width;
1781            self.cache.store_intrinsic(kind, value);
1782            value
1783        } else {
1784            0.0
1785        }
1786    }
1787
1788    fn max_intrinsic_width(&self, height: f32) -> f32 {
1789        let kind = IntrinsicKind::MaxWidth(height);
1790        self.cache.activate(self.cache_epoch);
1791        if let Some(value) = self.cache.get_intrinsic(&kind) {
1792            return value;
1793        }
1794        let constraints = Constraints {
1795            min_width: 0.0,
1796            max_width: f32::INFINITY,
1797            min_height: 0.0,
1798            max_height: height,
1799        };
1800        if let Some(node) = self.intrinsic_measure(constraints) {
1801            let value = node.size.width;
1802            self.cache.store_intrinsic(kind, value);
1803            value
1804        } else {
1805            0.0
1806        }
1807    }
1808
1809    fn min_intrinsic_height(&self, width: f32) -> f32 {
1810        let kind = IntrinsicKind::MinHeight(width);
1811        self.cache.activate(self.cache_epoch);
1812        if let Some(value) = self.cache.get_intrinsic(&kind) {
1813            return value;
1814        }
1815        let constraints = Constraints {
1816            min_width: width,
1817            max_width: width,
1818            min_height: 0.0,
1819            max_height: f32::INFINITY,
1820        };
1821        if let Some(node) = self.intrinsic_measure(constraints) {
1822            let value = node.size.height;
1823            self.cache.store_intrinsic(kind, value);
1824            value
1825        } else {
1826            0.0
1827        }
1828    }
1829
1830    fn max_intrinsic_height(&self, width: f32) -> f32 {
1831        let kind = IntrinsicKind::MaxHeight(width);
1832        self.cache.activate(self.cache_epoch);
1833        if let Some(value) = self.cache.get_intrinsic(&kind) {
1834            return value;
1835        }
1836        let constraints = Constraints {
1837            min_width: 0.0,
1838            max_width: width,
1839            min_height: 0.0,
1840            max_height: f32::INFINITY,
1841        };
1842        if let Some(node) = self.intrinsic_measure(constraints) {
1843            let value = node.size.height;
1844            self.cache.store_intrinsic(kind, value);
1845            value
1846        } else {
1847            0.0
1848        }
1849    }
1850
1851    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
1852        // Try to borrow the applier - if it's already borrowed (nested measurement), return None.
1853        // This is safe because parent data doesn't change during measurement.
1854        let Ok(mut applier) = self.applier.try_borrow_typed() else {
1855            return None;
1856        };
1857
1858        applier
1859            .with_node::<LayoutNode, _>(self.node_id, |layout_node| {
1860                let props = layout_node.resolved_modifiers().layout_properties();
1861                props.weight().map(|weight_data| {
1862                    cranpose_ui_layout::FlexParentData::new(weight_data.weight, weight_data.fill)
1863                })
1864            })
1865            .ok()
1866            .flatten()
1867    }
1868}
1869
1870fn measure_node_with_host(
1871    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1872    runtime_handle: Option<RuntimeHandle>,
1873    node_id: NodeId,
1874    constraints: Constraints,
1875    epoch: u64,
1876) -> Result<Rc<MeasuredNode>, NodeError> {
1877    let runtime_handle = match runtime_handle {
1878        Some(handle) => Some(handle),
1879        None => applier.borrow_typed().runtime_handle(),
1880    };
1881    let mut builder = LayoutBuilder::new_with_epoch(
1882        applier,
1883        epoch,
1884        Rc::new(RefCell::new(SlotBackend::default())),
1885    );
1886    builder.set_runtime_handle(runtime_handle);
1887    builder.measure_node(node_id, constraints)
1888}
1889
1890#[derive(Clone)]
1891struct RuntimeNodeMetadata {
1892    modifier: Modifier,
1893    resolved_modifiers: ResolvedModifiers,
1894    modifier_slices: Rc<ModifierNodeSlices>,
1895    role: SemanticsRole,
1896    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
1897}
1898
1899impl Default for RuntimeNodeMetadata {
1900    fn default() -> Self {
1901        Self {
1902            modifier: Modifier::empty(),
1903            resolved_modifiers: ResolvedModifiers::default(),
1904            modifier_slices: Rc::default(),
1905            role: SemanticsRole::Unknown,
1906            button_handler: None,
1907        }
1908    }
1909}
1910
1911fn collect_runtime_metadata(
1912    applier: &mut MemoryApplier,
1913    node: &MeasuredNode,
1914) -> Result<HashMap<NodeId, RuntimeNodeMetadata>, NodeError> {
1915    let mut map = HashMap::default();
1916    collect_runtime_metadata_inner(applier, node, &mut map)?;
1917    Ok(map)
1918}
1919
1920/// Collects semantics configurations for all nodes in the measured tree using the SemanticsOwner cache.
1921fn collect_semantics_with_owner(
1922    applier: &mut MemoryApplier,
1923    node: &MeasuredNode,
1924    owner: &SemanticsOwner,
1925) -> Result<(), NodeError> {
1926    // Compute and cache configuration for this node
1927    owner.get_or_compute(node.node_id, applier);
1928
1929    // Recurse to children
1930    for child in &node.children {
1931        collect_semantics_with_owner(applier, &child.node, owner)?;
1932    }
1933    Ok(())
1934}
1935
1936fn collect_semantics_snapshot(
1937    applier: &mut MemoryApplier,
1938    node: &MeasuredNode,
1939) -> Result<HashMap<NodeId, Option<SemanticsConfiguration>>, NodeError> {
1940    let owner = SemanticsOwner::new();
1941    collect_semantics_with_owner(applier, node, &owner)?;
1942
1943    // Extract all cached configurations into a map
1944    let mut map = HashMap::default();
1945    extract_configurations_recursive(node, &owner, &mut map);
1946    Ok(map)
1947}
1948
1949fn extract_configurations_recursive(
1950    node: &MeasuredNode,
1951    owner: &SemanticsOwner,
1952    map: &mut HashMap<NodeId, Option<SemanticsConfiguration>>,
1953) {
1954    if let Some(config) = owner.configurations.borrow().get(&node.node_id) {
1955        map.insert(node.node_id, config.clone());
1956    }
1957    for child in &node.children {
1958        extract_configurations_recursive(&child.node, owner, map);
1959    }
1960}
1961
1962fn collect_runtime_metadata_inner(
1963    applier: &mut MemoryApplier,
1964    node: &MeasuredNode,
1965    map: &mut HashMap<NodeId, RuntimeNodeMetadata>,
1966) -> Result<(), NodeError> {
1967    if let Entry::Vacant(entry) = map.entry(node.node_id) {
1968        let meta = runtime_metadata_for(applier, node.node_id)?;
1969        entry.insert(meta);
1970    }
1971    for child in &node.children {
1972        collect_runtime_metadata_inner(applier, &child.node, map)?;
1973    }
1974    Ok(())
1975}
1976
1977fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
1978    modifier_slices
1979        .text_content()
1980        .map(|text| SemanticsRole::Text {
1981            value: text.to_string(),
1982        })
1983        .unwrap_or(SemanticsRole::Layout)
1984}
1985
1986fn runtime_metadata_for(
1987    applier: &mut MemoryApplier,
1988    node_id: NodeId,
1989) -> Result<RuntimeNodeMetadata, NodeError> {
1990    // Try LayoutNode (the primary modern path)
1991    // IMPORTANT: We use with_node (reference) instead of try_clone because cloning
1992    // LayoutNode creates a NEW ModifierChainHandle with NEW nodes and NEW handlers,
1993    // which would lose gesture state like press_position.
1994    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
1995        let modifier = layout.modifier.clone();
1996        let resolved_modifiers = layout.resolved_modifiers();
1997        let modifier_slices = layout.modifier_slices_snapshot();
1998        let role = role_from_modifier_slices(&modifier_slices);
1999
2000        RuntimeNodeMetadata {
2001            modifier,
2002            resolved_modifiers,
2003            modifier_slices,
2004            role,
2005            button_handler: None,
2006        }
2007    }) {
2008        return Ok(meta);
2009    }
2010
2011    // Try SubcomposeLayoutNode
2012    if let Ok((modifier, resolved_modifiers, modifier_slices)) = applier
2013        .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2014            (
2015                node.modifier(),
2016                node.resolved_modifiers(),
2017                node.modifier_slices_snapshot(),
2018            )
2019        })
2020    {
2021        return Ok(RuntimeNodeMetadata {
2022            modifier,
2023            resolved_modifiers,
2024            modifier_slices,
2025            role: SemanticsRole::Subcompose,
2026            button_handler: None,
2027        });
2028    }
2029    Ok(RuntimeNodeMetadata::default())
2030}
2031
2032/// Computes semantics configuration for a node by reading from its modifier chain.
2033/// This is the primary entry point for extracting semantics from nodes, replacing
2034/// the widget-specific fallbacks with pure modifier-node traversal.
2035fn compute_semantics_for_node(
2036    applier: &mut MemoryApplier,
2037    node_id: NodeId,
2038) -> Option<SemanticsConfiguration> {
2039    // Try LayoutNode (the primary modern path)
2040    match applier.with_node::<LayoutNode, _>(node_id, |layout| {
2041        let config = layout.semantics_configuration();
2042        layout.clear_needs_semantics();
2043        config
2044    }) {
2045        Ok(config) => return config,
2046        Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
2047        Err(_) => return None,
2048    }
2049
2050    // Try SubcomposeLayoutNode
2051    if let Ok(modifier) =
2052        applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| node.modifier())
2053    {
2054        return collect_semantics_from_modifier(&modifier);
2055    }
2056
2057    None
2058}
2059
2060/// Builds a semantics node from measured tree data and semantics configurations.
2061/// Roles and actions are now derived entirely from SemanticsConfiguration, with
2062/// metadata consulted only for prior widget type information.
2063fn build_semantics_node(
2064    node: &MeasuredNode,
2065    metadata: &HashMap<NodeId, RuntimeNodeMetadata>,
2066    semantics: &HashMap<NodeId, Option<SemanticsConfiguration>>,
2067) -> SemanticsNode {
2068    let info = metadata.get(&node.node_id).cloned().unwrap_or_default();
2069
2070    // Start with the widget-derived role as a fallback
2071    let mut role = info.role.clone();
2072    let mut actions = Vec::new();
2073    let mut description = None;
2074
2075    // Override with semantics configuration if present
2076    if let Some(config) = semantics.get(&node.node_id).cloned().flatten() {
2077        // Role synthesis: prefer semantics flags over widget type
2078        if config.is_button {
2079            role = SemanticsRole::Button;
2080        }
2081
2082        // Action synthesis: create click action if node is clickable
2083        if config.is_clickable {
2084            actions.push(SemanticsAction::Click {
2085                handler: SemanticsCallback::new(node.node_id),
2086            });
2087        }
2088
2089        // Description from configuration
2090        if let Some(desc) = config.content_description {
2091            description = Some(desc);
2092        }
2093    }
2094
2095    let children = node
2096        .children
2097        .iter()
2098        .map(|child| build_semantics_node(&child.node, metadata, semantics))
2099        .collect();
2100
2101    SemanticsNode::new(node.node_id, role, actions, children, description)
2102}
2103
2104fn build_layout_tree_from_metadata(
2105    node: &MeasuredNode,
2106    metadata: &HashMap<NodeId, RuntimeNodeMetadata>,
2107) -> LayoutTree {
2108    fn place(
2109        node: &MeasuredNode,
2110        origin: Point,
2111        metadata: &HashMap<NodeId, RuntimeNodeMetadata>,
2112    ) -> LayoutBox {
2113        // Include the node's own offset (from OffsetNode) in its position
2114        let top_left = Point {
2115            x: origin.x + node.offset.x,
2116            y: origin.y + node.offset.y,
2117        };
2118        let rect = GeometryRect {
2119            x: top_left.x,
2120            y: top_left.y,
2121            width: node.size.width,
2122            height: node.size.height,
2123        };
2124        let info = metadata.get(&node.node_id).cloned().unwrap_or_default();
2125        let kind = layout_kind_from_metadata(node.node_id, &info);
2126        let data = LayoutNodeData::new(
2127            info.modifier.clone(),
2128            info.resolved_modifiers,
2129            info.modifier_slices.clone(),
2130            kind,
2131        );
2132        let children = node
2133            .children
2134            .iter()
2135            .map(|child| {
2136                let child_origin = Point {
2137                    x: top_left.x + child.offset.x,
2138                    y: top_left.y + child.offset.y,
2139                };
2140                place(&child.node, child_origin, metadata)
2141            })
2142            .collect();
2143        LayoutBox::new(node.node_id, rect, node.content_offset, data, children)
2144    }
2145
2146    LayoutTree::new(place(node, Point { x: 0.0, y: 0.0 }, metadata))
2147}
2148
2149fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
2150    match &info.role {
2151        SemanticsRole::Layout => LayoutNodeKind::Layout,
2152        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
2153        SemanticsRole::Text { .. } => {
2154            // Text content is now handled via TextModifierNode in the modifier chain
2155            // and collected in modifier_slices.text_content(). LayoutNodeKind should
2156            // reflect the layout policy (EmptyMeasurePolicy), not the content type.
2157            LayoutNodeKind::Layout
2158        }
2159        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
2160        SemanticsRole::Button => {
2161            let handler = info
2162                .button_handler
2163                .as_ref()
2164                .cloned()
2165                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
2166            LayoutNodeKind::Button { on_click: handler }
2167        }
2168        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
2169    }
2170}
2171
2172fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
2173    let horizontal = padding.horizontal_sum();
2174    let vertical = padding.vertical_sum();
2175    let min_width = (constraints.min_width - horizontal).max(0.0);
2176    let mut max_width = constraints.max_width;
2177    if max_width.is_finite() {
2178        max_width = (max_width - horizontal).max(0.0);
2179    }
2180    let min_height = (constraints.min_height - vertical).max(0.0);
2181    let mut max_height = constraints.max_height;
2182    if max_height.is_finite() {
2183        max_height = (max_height - vertical).max(0.0);
2184    }
2185    normalize_constraints(Constraints {
2186        min_width,
2187        max_width,
2188        min_height,
2189        max_height,
2190    })
2191}
2192
2193#[cfg(test)]
2194pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
2195    match alignment {
2196        HorizontalAlignment::Start => 0.0,
2197        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
2198        HorizontalAlignment::End => (available - child).max(0.0),
2199    }
2200}
2201
2202#[cfg(test)]
2203pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
2204    match alignment {
2205        VerticalAlignment::Top => 0.0,
2206        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
2207        VerticalAlignment::Bottom => (available - child).max(0.0),
2208    }
2209}
2210
2211fn resolve_dimension(
2212    base: f32,
2213    explicit: DimensionConstraint,
2214    min_override: Option<f32>,
2215    max_override: Option<f32>,
2216    min_limit: f32,
2217    max_limit: f32,
2218) -> f32 {
2219    let mut min_bound = min_limit;
2220    if let Some(min_value) = min_override {
2221        min_bound = min_bound.max(min_value);
2222    }
2223
2224    let mut max_bound = if max_limit.is_finite() {
2225        max_limit
2226    } else {
2227        max_override.unwrap_or(max_limit)
2228    };
2229    if let Some(max_value) = max_override {
2230        if max_bound.is_finite() {
2231            max_bound = max_bound.min(max_value);
2232        } else {
2233            max_bound = max_value;
2234        }
2235    }
2236    if max_bound < min_bound {
2237        max_bound = min_bound;
2238    }
2239
2240    let mut size = match explicit {
2241        DimensionConstraint::Points(points) => points,
2242        DimensionConstraint::Fraction(fraction) => {
2243            if max_limit.is_finite() {
2244                max_limit * fraction.clamp(0.0, 1.0)
2245            } else {
2246                base
2247            }
2248        }
2249        DimensionConstraint::Unspecified => base,
2250        // Intrinsic sizing is resolved at a higher level where we have access to children.
2251        // At this point we just use the base size as a fallback.
2252        DimensionConstraint::Intrinsic(_) => base,
2253    };
2254
2255    size = clamp_dimension(size, min_bound, max_bound);
2256    size = clamp_dimension(size, min_limit, max_limit);
2257    size.max(0.0)
2258}
2259
2260fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
2261    let mut result = value.max(min);
2262    if max.is_finite() {
2263        result = result.min(max);
2264    }
2265    result
2266}
2267
2268fn normalize_constraints(mut constraints: Constraints) -> Constraints {
2269    if constraints.max_width < constraints.min_width {
2270        constraints.max_width = constraints.min_width;
2271    }
2272    if constraints.max_height < constraints.min_height {
2273        constraints.max_height = constraints.min_height;
2274    }
2275    constraints
2276}
2277
2278#[cfg(test)]
2279#[path = "tests/layout_tests.rs"]
2280mod tests;