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