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::HashMap;
8use std::{
9    cell::{Cell, RefCell},
10    fmt,
11    mem::size_of,
12    rc::Rc,
13    sync::atomic::{AtomicU64, Ordering},
14};
15
16use cranpose_core::{
17    Applier, ApplierHost, Composer, ConcreteApplierHost, MemoryApplier, Node, NodeError, NodeId,
18    Phase, RuntimeHandle, SlotTable, 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    ModifierNodeSlicesDebugStats, Point, Rect as GeometryRect, ResolvedModifiers, Size,
29};
30
31use crate::subcompose_layout::SubcomposeLayoutNode;
32use crate::widgets::nodes::{IntrinsicKind, LayoutNode, LayoutNodeCacheHandles};
33use cranpose_foundation::{
34    InvalidationKind, ModifierNodeContext, NodeCapabilities, SemanticsConfiguration,
35};
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 `SlotTable` 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<SlotTable>>`,
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<SlotTable>>,
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<SlotTable>> {
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, PartialEq, Eq)]
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, PartialEq, Eq)]
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#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
310pub struct LayoutAllocationDebugStats {
311    pub layout_box_count: usize,
312    pub layout_box_child_count: usize,
313    pub layout_box_child_capacity: usize,
314    pub layout_box_heap_bytes: usize,
315    pub modifier_slice_count: usize,
316    pub modifier_slice_heap_bytes: usize,
317    pub modifier_draw_command_count: usize,
318    pub modifier_draw_command_capacity: usize,
319    pub modifier_pointer_input_count: usize,
320    pub modifier_pointer_input_capacity: usize,
321    pub modifier_click_handler_count: usize,
322    pub modifier_click_handler_capacity: usize,
323    pub modifier_text_content_count: usize,
324    pub modifier_text_style_count: usize,
325    pub modifier_text_layout_options_count: usize,
326    pub modifier_prepared_text_layout_count: usize,
327    pub modifier_graphics_layer_count: usize,
328    pub modifier_graphics_layer_resolver_count: usize,
329    pub semantics_node_count: usize,
330    pub semantics_action_count: usize,
331    pub semantics_action_capacity: usize,
332    pub semantics_child_count: usize,
333    pub semantics_child_capacity: usize,
334    pub semantics_description_count: usize,
335    pub semantics_description_bytes: usize,
336    pub semantics_text_role_bytes: usize,
337    pub semantics_heap_bytes: usize,
338}
339
340impl LayoutAllocationDebugStats {
341    fn add_modifier_slice(&mut self, stats: ModifierNodeSlicesDebugStats) {
342        self.modifier_slice_count += 1;
343        self.modifier_slice_heap_bytes += stats.heap_bytes;
344        self.modifier_draw_command_count += stats.draw_command_count;
345        self.modifier_draw_command_capacity += stats.draw_command_capacity;
346        self.modifier_pointer_input_count += stats.pointer_input_count;
347        self.modifier_pointer_input_capacity += stats.pointer_input_capacity;
348        self.modifier_click_handler_count += stats.click_handler_count;
349        self.modifier_click_handler_capacity += stats.click_handler_capacity;
350        self.modifier_text_content_count += usize::from(stats.has_text_content);
351        self.modifier_text_style_count += usize::from(stats.has_text_style);
352        self.modifier_text_layout_options_count += usize::from(stats.has_text_layout_options);
353        self.modifier_prepared_text_layout_count += usize::from(stats.has_prepared_text_layout);
354        self.modifier_graphics_layer_count += usize::from(stats.has_graphics_layer);
355        self.modifier_graphics_layer_resolver_count +=
356            usize::from(stats.has_graphics_layer_resolver);
357    }
358}
359
360/// Result of running layout for a Compose tree.
361#[derive(Debug, Clone)]
362pub struct LayoutTree {
363    root: LayoutBox,
364}
365
366impl LayoutTree {
367    pub fn new(root: LayoutBox) -> Self {
368        Self { root }
369    }
370
371    pub fn root(&self) -> &LayoutBox {
372        &self.root
373    }
374
375    pub fn root_mut(&mut self) -> &mut LayoutBox {
376        &mut self.root
377    }
378
379    pub fn into_root(self) -> LayoutBox {
380        self.root
381    }
382
383    pub fn debug_allocation_stats(&self) -> LayoutAllocationDebugStats {
384        let mut stats = LayoutAllocationDebugStats::default();
385        record_layout_box_allocation_stats(&self.root, &mut stats);
386        stats
387    }
388}
389
390/// Layout information for a single node.
391#[derive(Debug, Clone)]
392pub struct LayoutBox {
393    pub node_id: NodeId,
394    pub rect: GeometryRect,
395    /// Content offset for scroll/inner transforms (applies to children, NOT this node's position)
396    pub content_offset: Point,
397    pub node_data: LayoutNodeData,
398    pub children: Vec<LayoutBox>,
399}
400
401impl LayoutBox {
402    pub fn new(
403        node_id: NodeId,
404        rect: GeometryRect,
405        content_offset: Point,
406        node_data: LayoutNodeData,
407        children: Vec<LayoutBox>,
408    ) -> Self {
409        Self {
410            node_id,
411            rect,
412            content_offset,
413            node_data,
414            children,
415        }
416    }
417}
418
419/// Snapshot of the data required to render a layout node.
420#[derive(Debug, Clone)]
421pub struct LayoutNodeData {
422    pub modifier: Modifier,
423    pub resolved_modifiers: ResolvedModifiers,
424    pub modifier_slices: Rc<ModifierNodeSlices>,
425    pub kind: LayoutNodeKind,
426}
427
428impl LayoutNodeData {
429    pub fn new(
430        modifier: Modifier,
431        resolved_modifiers: ResolvedModifiers,
432        modifier_slices: Rc<ModifierNodeSlices>,
433        kind: LayoutNodeKind,
434    ) -> Self {
435        Self {
436            modifier,
437            resolved_modifiers,
438            modifier_slices,
439            kind,
440        }
441    }
442
443    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
444        self.resolved_modifiers
445    }
446
447    pub fn modifier_slices(&self) -> &ModifierNodeSlices {
448        &self.modifier_slices
449    }
450}
451
452/// Classification of the node captured inside a [`LayoutBox`].
453///
454/// Note: Text content is no longer represented as a distinct LayoutNodeKind.
455/// Text nodes now use `LayoutNodeKind::Layout` with their content stored in
456/// `modifier_slices.text_content()` via TextModifierNode, following Jetpack
457/// Compose's pattern where text is a modifier node capability.
458#[derive(Clone)]
459pub enum LayoutNodeKind {
460    Layout,
461    Subcompose,
462    Spacer,
463    Button { on_click: Rc<RefCell<dyn FnMut()>> },
464    Unknown,
465}
466
467impl fmt::Debug for LayoutNodeKind {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        match self {
470            LayoutNodeKind::Layout => f.write_str("Layout"),
471            LayoutNodeKind::Subcompose => f.write_str("Subcompose"),
472            LayoutNodeKind::Spacer => f.write_str("Spacer"),
473            LayoutNodeKind::Button { .. } => f.write_str("Button"),
474            LayoutNodeKind::Unknown => f.write_str("Unknown"),
475        }
476    }
477}
478
479/// Extension trait that equips `MemoryApplier` with layout computation.
480pub trait LayoutEngine {
481    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError>;
482}
483
484impl LayoutEngine for MemoryApplier {
485    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError> {
486        let measurements = measure_layout(self, root, max_size)?;
487        Ok(measurements.into_layout_tree())
488    }
489}
490
491/// Result of running the measure pass for a Compose layout tree.
492#[derive(Debug, Clone)]
493pub struct LayoutMeasurements {
494    root: Rc<MeasuredNode>,
495    semantics: Option<SemanticsTree>,
496    layout_tree: Option<LayoutTree>,
497}
498
499impl LayoutMeasurements {
500    fn new(
501        root: Rc<MeasuredNode>,
502        semantics: Option<SemanticsTree>,
503        layout_tree: Option<LayoutTree>,
504    ) -> Self {
505        Self {
506            root,
507            semantics,
508            layout_tree,
509        }
510    }
511
512    /// Returns the measured size of the root node.
513    pub fn root_size(&self) -> Size {
514        self.root.size
515    }
516
517    pub fn semantics_tree(&self) -> Option<&SemanticsTree> {
518        self.semantics.as_ref()
519    }
520
521    pub fn debug_allocation_stats(&self) -> LayoutAllocationDebugStats {
522        let mut stats = self
523            .layout_tree
524            .as_ref()
525            .map(LayoutTree::debug_allocation_stats)
526            .unwrap_or_default();
527        if let Some(semantics) = &self.semantics {
528            record_semantics_allocation_stats(semantics.root(), &mut stats);
529        }
530        stats
531    }
532
533    /// Consumes the measurements and produces a [`LayoutTree`].
534    pub fn into_layout_tree(self) -> LayoutTree {
535        self.layout_tree
536            .expect("layout tree was not built for these measurements")
537    }
538
539    /// Returns a borrowed [`LayoutTree`] for rendering.
540    pub fn layout_tree(&self) -> LayoutTree {
541        self.layout_tree
542            .clone()
543            .expect("layout tree was not built for these measurements")
544    }
545}
546
547/// Builds a semantics tree from an existing [`LayoutTree`].
548///
549/// This is useful for consumers that need semantics on demand without forcing
550/// every layout pass to eagerly allocate a full [`SemanticsTree`].
551pub fn build_semantics_tree_from_layout_tree(layout_tree: &LayoutTree) -> SemanticsTree {
552    SemanticsTree::new(build_semantics_node_from_layout_box(layout_tree.root()))
553}
554
555/// Builds a layout snapshot from retained layout state in the live applier tree.
556///
557/// Renderers use retained node state directly. This function exists for debug,
558/// robot, and tests that need an owned [`LayoutTree`] without forcing every
559/// layout pass to allocate one.
560pub fn build_layout_tree_from_applier(
561    applier: &mut MemoryApplier,
562    root: NodeId,
563) -> Result<Option<LayoutTree>, NodeError> {
564    fn snapshot(
565        applier: &mut MemoryApplier,
566        node_id: NodeId,
567    ) -> Result<Option<(crate::widgets::nodes::layout_node::LayoutState, Vec<NodeId>)>, NodeError>
568    {
569        match applier.with_node::<LayoutNode, _>(node_id, |node| {
570            (node.layout_state(), node.children.clone())
571        }) {
572            Ok(snapshot) => return Ok(Some(snapshot)),
573            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
574            Err(err) => return Err(err),
575        }
576
577        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
578            (node.layout_state(), node.active_children())
579        }) {
580            Ok(snapshot) => Ok(Some(snapshot)),
581            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
582            Err(err) => Err(err),
583        }
584    }
585
586    fn place(
587        applier: &mut MemoryApplier,
588        node_id: NodeId,
589        parent_content_origin: Point,
590    ) -> Result<Option<LayoutBox>, NodeError> {
591        let Some((state, child_ids)) = snapshot(applier, node_id)? else {
592            return Ok(None);
593        };
594        if !state.is_placed {
595            return Ok(None);
596        }
597
598        let top_left = Point {
599            x: parent_content_origin.x + state.position.x,
600            y: parent_content_origin.y + state.position.y,
601        };
602        let rect = GeometryRect {
603            x: top_left.x,
604            y: top_left.y,
605            width: state.size.width,
606            height: state.size.height,
607        };
608        let info = runtime_metadata_for(applier, node_id)?;
609        let kind = layout_kind_from_metadata(node_id, &info);
610        let RuntimeNodeMetadata {
611            modifier,
612            resolved_modifiers,
613            modifier_slices,
614            ..
615        } = info;
616        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
617        let child_origin = Point {
618            x: top_left.x + state.content_offset.x,
619            y: top_left.y + state.content_offset.y,
620        };
621        let mut children = Vec::with_capacity(child_ids.len());
622        for child_id in child_ids {
623            if let Some(child) = place(applier, child_id, child_origin)? {
624                children.push(child);
625            }
626        }
627
628        Ok(Some(LayoutBox::new(
629            node_id,
630            rect,
631            state.content_offset,
632            data,
633            children,
634        )))
635    }
636
637    place(applier, root, Point::default()).map(|root| root.map(LayoutTree::new))
638}
639
640/// Builds a semantics snapshot from retained layout state in the live applier tree.
641///
642/// This is the on-demand counterpart to [`build_layout_tree_from_applier`].
643/// It follows the currently placed child set, including subcompose active
644/// children, and clears semantics dirty flags for nodes it visits.
645pub fn build_semantics_tree_from_applier(
646    applier: &mut MemoryApplier,
647    root: NodeId,
648) -> Result<Option<SemanticsTree>, NodeError> {
649    fn node(
650        applier: &mut MemoryApplier,
651        node_id: NodeId,
652    ) -> Result<Option<SemanticsNode>, NodeError> {
653        match applier.with_node::<LayoutNode, _>(node_id, |layout| {
654            let state = layout.layout_state();
655            if !state.is_placed {
656                return None;
657            }
658            let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
659            let config = layout.semantics_configuration();
660            let children = layout.children.clone();
661            layout.clear_needs_semantics();
662            Some((role, config, children))
663        }) {
664            Ok(Some((role, config, child_ids))) => {
665                let mut children = Vec::with_capacity(child_ids.len());
666                for child_id in child_ids {
667                    if let Some(child) = node(applier, child_id)? {
668                        children.push(child);
669                    }
670                }
671                return Ok(Some(semantics_node_from_parts(
672                    node_id, role, config, children,
673                )));
674            }
675            Ok(None) => return Ok(None),
676            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
677            Err(err) => return Err(err),
678        }
679
680        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |subcompose| {
681            let state = subcompose.layout_state();
682            if !state.is_placed {
683                return None;
684            }
685            let config = collect_semantics_from_modifier(&subcompose.modifier());
686            let children = subcompose.active_children();
687            subcompose.clear_needs_semantics();
688            Some((config, children))
689        }) {
690            Ok(Some((config, child_ids))) => {
691                let mut children = Vec::with_capacity(child_ids.len());
692                for child_id in child_ids {
693                    if let Some(child) = node(applier, child_id)? {
694                        children.push(child);
695                    }
696                }
697                Ok(Some(semantics_node_from_parts(
698                    node_id,
699                    SemanticsRole::Subcompose,
700                    config,
701                    children,
702                )))
703            }
704            Ok(None) | Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
705                Ok(None)
706            }
707            Err(err) => Err(err),
708        }
709    }
710
711    node(applier, root).map(|root| root.map(SemanticsTree::new))
712}
713
714#[derive(Clone, Copy, Debug, PartialEq, Eq)]
715pub struct MeasureLayoutOptions {
716    pub collect_semantics: bool,
717    pub build_layout_tree: bool,
718}
719
720impl Default for MeasureLayoutOptions {
721    fn default() -> Self {
722        Self {
723            collect_semantics: true,
724            build_layout_tree: true,
725        }
726    }
727}
728
729/// Check if a node or any of its descendants needs measure (selective measure optimization).
730/// This can be used by the app shell to skip layout when the tree is clean.
731///
732/// O(1) check - just looks at root's dirty flag.
733/// Works because all mutation paths bubble dirty flags to root via composer commands.
734///
735/// Returns Result to force caller to handle errors explicitly. No more unwrap_or(true) safety net.
736pub fn tree_needs_layout(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
737    Ok(applier.get_mut(root)?.needs_layout())
738}
739
740/// Check if the root semantics snapshot is dirty.
741///
742/// Semantics invalidations bubble to the root the same way layout invalidations do,
743/// so a root check is sufficient to determine whether the next layout pass needs to
744/// rebuild semantic data even when geometry is otherwise unchanged.
745pub fn tree_needs_semantics(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
746    Ok(applier.get_mut(root)?.needs_semantics())
747}
748
749/// Test helper: bubbles layout dirty flag to root.
750#[cfg(test)]
751pub(crate) fn bubble_layout_dirty(applier: &mut MemoryApplier, node_id: NodeId) {
752    cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
753}
754
755/// Runs the measure phase for the subtree rooted at `root`.
756pub fn measure_layout(
757    applier: &mut MemoryApplier,
758    root: NodeId,
759    max_size: Size,
760) -> Result<LayoutMeasurements, NodeError> {
761    measure_layout_with_options(applier, root, max_size, MeasureLayoutOptions::default())
762}
763
764pub fn measure_layout_with_options(
765    applier: &mut MemoryApplier,
766    root: NodeId,
767    max_size: Size,
768    options: MeasureLayoutOptions,
769) -> Result<LayoutMeasurements, NodeError> {
770    process_pending_layout_repasses(applier, root)?;
771
772    let constraints = Constraints {
773        min_width: 0.0,
774        max_width: max_size.width,
775        min_height: 0.0,
776        max_height: max_size.height,
777    };
778
779    // Selective measure: only increment epoch if something needs MEASURING (not just layout)
780    // O(1) check - just look at root's dirty flag (bubbling ensures correctness)
781    //
782    // CRITICAL: We check needs_MEASURE, not needs_LAYOUT!
783    // - needs_measure: size may change, caches must be invalidated
784    // - needs_layout: position may change but size is cached (e.g., scroll)
785    //
786    // Scroll operations bubble needs_layout to ancestors, but NOT needs_measure.
787    // Using needs_layout here would wipe ALL caches on every scroll frame, causing
788    // O(N) full remeasurement instead of O(changed nodes).
789    let (needs_remeasure, _needs_semantics, cached_epoch) = match applier
790        .with_node::<LayoutNode, _>(root, |node| {
791            (
792                node.needs_measure(), // CORRECT: check needs_measure, not needs_layout
793                node.needs_semantics(),
794                node.cache_handles().epoch(),
795            )
796        }) {
797        Ok(tuple) => tuple,
798        Err(NodeError::TypeMismatch { .. }) => {
799            let node = applier.get_mut(root)?;
800            // Non-LayoutNode roots still expose Node dirty flags.
801            // Use needs_measure here so layout-only subtree repasses can reuse
802            // the existing cache epoch instead of invalidating the whole tree.
803            let measure_dirty = node.needs_measure();
804            let semantics_dirty = node.needs_semantics();
805            (measure_dirty, semantics_dirty, 0)
806        }
807        Err(err) => return Err(err),
808    };
809
810    let epoch = if needs_remeasure {
811        NEXT_CACHE_EPOCH.fetch_add(1, Ordering::Relaxed)
812    } else if cached_epoch != 0 {
813        cached_epoch
814    } else {
815        // Fallback when caller root isn't a LayoutNode (e.g. tests using Spacer directly).
816        NEXT_CACHE_EPOCH.load(Ordering::Relaxed)
817    };
818
819    // Move the current applier into a host and set up a guard that will
820    // ALWAYS restore:
821    // - the MemoryApplier back into `applier`
822    // - the SlotTable back into that MemoryApplier
823    //
824    // IMPORTANT: Declare the guard *before* the builder so the builder
825    // is dropped first (both on Ok and on unwind).
826    let guard = ApplierSlotGuard::new(applier);
827    let applier_host = guard.host();
828    let slots_handle = guard.slots_handle();
829
830    // Give the builder the shared slots handle - both guard and builder
831    // now share access to the same SlotTable via Rc<RefCell<_>>.
832    let mut builder =
833        LayoutBuilder::new_with_epoch(Rc::clone(&applier_host), epoch, Rc::clone(&slots_handle));
834
835    // ---- Measurement -------------------------------------------------------
836    // If measurement fails, the guard will restore slots from the shared handle
837    // on drop - this is safe because the handle always contains valid slots.
838
839    let measured = builder.measure_node(root, normalize_constraints(constraints))?;
840
841    // Root node has no parent to place it, so we must explicitly place it at (0,0).
842    // This ensures is_placed=true, allowing the renderer to traverse the tree.
843    // Handle both LayoutNode and SubcomposeLayoutNode as potential roots.
844    if let Ok(mut applier) = applier_host.try_borrow_typed() {
845        if applier
846            .with_node::<LayoutNode, _>(root, |node| {
847                node.set_position(Point::default());
848            })
849            .is_err()
850        {
851            let _ = applier.with_node::<SubcomposeLayoutNode, _>(root, |node| {
852                node.set_position(Point::default());
853            });
854        }
855    }
856
857    let (layout_tree, semantics) = {
858        let mut applier_ref = applier_host.borrow_typed();
859        let layout_tree = if options.build_layout_tree {
860            Some(build_layout_tree(&mut applier_ref, &measured)?)
861        } else {
862            None
863        };
864        let semantics = if options.collect_semantics {
865            let semantics_tree = if let Some(layout_tree) = layout_tree.as_ref() {
866                clear_semantics_dirty_flags(&mut applier_ref, &measured)?;
867                build_semantics_tree_from_layout_tree(layout_tree)
868            } else {
869                build_semantics_tree_from_live_nodes(&mut applier_ref, &measured)?
870            };
871            Some(semantics_tree)
872        } else {
873            None
874        };
875        (layout_tree, semantics)
876    };
877
878    // Drop builder before guard - slots are already in the shared handle.
879    // Guard's Drop will write them back to the applier.
880    drop(builder);
881
882    // DO NOT manually unwrap `applier_host` or replace `applier` here.
883    // `ApplierSlotGuard::drop` will restore everything when this function returns.
884
885    Ok(LayoutMeasurements::new(measured, semantics, layout_tree))
886}
887
888fn process_pending_layout_repasses(
889    applier: &mut MemoryApplier,
890    root: NodeId,
891) -> Result<(), NodeError> {
892    let repass_nodes = crate::take_layout_repass_nodes();
893    if repass_nodes.is_empty() {
894        return Ok(());
895    }
896    for node_id in repass_nodes {
897        cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
898    }
899    applier.get_mut(root)?.mark_needs_layout();
900    Ok(())
901}
902
903struct LayoutBuilder {
904    state: Rc<RefCell<LayoutBuilderState>>,
905}
906
907impl LayoutBuilder {
908    fn new_with_epoch(
909        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
910        epoch: u64,
911        slots: Rc<RefCell<SlotTable>>,
912    ) -> Self {
913        Self {
914            state: Rc::new(RefCell::new(LayoutBuilderState::new_with_epoch(
915                applier, epoch, slots,
916            ))),
917        }
918    }
919
920    fn measure_node(
921        &mut self,
922        node_id: NodeId,
923        constraints: Constraints,
924    ) -> Result<Rc<MeasuredNode>, NodeError> {
925        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
926    }
927
928    fn set_runtime_handle(&mut self, handle: Option<RuntimeHandle>) {
929        self.state.borrow_mut().runtime_handle = handle;
930    }
931}
932
933struct LayoutBuilderState {
934    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
935    runtime_handle: Option<RuntimeHandle>,
936    /// Shared handle to the slot table. This is shared with ApplierSlotGuard
937    /// to ensure panic-safety: even if we panic, the guard can restore slots.
938    slots: Rc<RefCell<SlotTable>>,
939    cache_epoch: u64,
940    tmp_measurables: ScratchVecPool<Box<dyn Measurable>>,
941    tmp_records: ScratchVecPool<(NodeId, ChildRecord)>,
942    tmp_child_ids: ScratchVecPool<NodeId>,
943    tmp_layout_node_data: ScratchVecPool<LayoutModifierNodeData>,
944}
945
946impl LayoutBuilderState {
947    fn new_with_epoch(
948        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
949        epoch: u64,
950        slots: Rc<RefCell<SlotTable>>,
951    ) -> Self {
952        let runtime_handle = applier.borrow_typed().runtime_handle();
953
954        Self {
955            applier,
956            runtime_handle,
957            slots,
958            cache_epoch: epoch,
959            tmp_measurables: ScratchVecPool::default(),
960            tmp_records: ScratchVecPool::default(),
961            tmp_child_ids: ScratchVecPool::default(),
962            tmp_layout_node_data: ScratchVecPool::default(),
963        }
964    }
965
966    fn try_with_applier_result<R>(
967        state_rc: &Rc<RefCell<Self>>,
968        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
969    ) -> Option<Result<R, NodeError>> {
970        let host = {
971            let state = state_rc.borrow();
972            Rc::clone(&state.applier)
973        };
974
975        // Try to borrow - if already borrowed (nested call), return None
976        let Ok(mut applier) = host.try_borrow_typed() else {
977            return None;
978        };
979
980        Some(f(&mut applier))
981    }
982
983    fn with_applier_result<R>(
984        state_rc: &Rc<RefCell<Self>>,
985        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
986    ) -> Result<R, NodeError> {
987        Self::try_with_applier_result(state_rc, f).unwrap_or_else(|| {
988            Err(NodeError::MissingContext {
989                id: NodeId::default(),
990                reason: "applier already borrowed",
991            })
992        })
993    }
994
995    /// Clears the is_placed flag for a node at the start of measurement.
996    /// This ensures nodes that drop out of placement won't render with stale geometry.
997    fn clear_node_placed(state_rc: &Rc<RefCell<Self>>, node_id: NodeId) {
998        let host = {
999            let state = state_rc.borrow();
1000            Rc::clone(&state.applier)
1001        };
1002        let Ok(mut applier) = host.try_borrow_typed() else {
1003            return;
1004        };
1005        // Try LayoutNode first, then SubcomposeLayoutNode
1006        if applier
1007            .with_node::<LayoutNode, _>(node_id, |node| {
1008                node.clear_placed();
1009            })
1010            .is_err()
1011        {
1012            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1013                node.clear_placed();
1014            });
1015        }
1016    }
1017
1018    fn measure_node(
1019        state_rc: Rc<RefCell<Self>>,
1020        node_id: NodeId,
1021        constraints: Constraints,
1022    ) -> Result<Rc<MeasuredNode>, NodeError> {
1023        // Clear is_placed at the start of measurement.
1024        // Nodes that are placed will have is_placed set to true via Placeable::place().
1025        // Nodes that drop out of placement (not placed this pass) will remain is_placed=false.
1026        Self::clear_node_placed(&state_rc, node_id);
1027
1028        // Try SubcomposeLayoutNode first
1029        if let Some(subcompose) =
1030            Self::try_measure_subcompose(Rc::clone(&state_rc), node_id, constraints)?
1031        {
1032            return Ok(subcompose);
1033        }
1034
1035        // Try LayoutNode (the primary modern path)
1036        if let Some(result) = Self::try_with_applier_result(&state_rc, |applier| {
1037            match applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1038                LayoutNodeSnapshot::from_layout_node(layout_node)
1039            }) {
1040                Ok(snapshot) => Ok(Some(snapshot)),
1041                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
1042                Err(err) => Err(err),
1043            }
1044        }) {
1045            // Applier was available, process the result
1046            if let Some(snapshot) = result? {
1047                return Self::measure_layout_node(
1048                    Rc::clone(&state_rc),
1049                    node_id,
1050                    snapshot,
1051                    constraints,
1052                );
1053            }
1054        }
1055        // If applier was busy (None) or snapshot was None, fall through to fallback
1056
1057        // No alternate fallbacks - all widgets use LayoutNode or SubcomposeLayoutNode
1058        // If we reach here, it's an unknown node type (shouldn't happen in normal use)
1059        Ok(Rc::new(MeasuredNode::new(
1060            node_id,
1061            Size::default(),
1062            Point { x: 0.0, y: 0.0 },
1063            Point::default(), // No content offset for fallback nodes
1064            Vec::new(),
1065        )))
1066    }
1067
1068    fn try_measure_subcompose(
1069        state_rc: Rc<RefCell<Self>>,
1070        node_id: NodeId,
1071        constraints: Constraints,
1072    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1073        let applier_host = {
1074            let state = state_rc.borrow();
1075            Rc::clone(&state.applier)
1076        };
1077
1078        let (node_handle, resolved_modifiers) = {
1079            // Try to borrow - if already borrowed (nested measurement), return None
1080            let Ok(mut applier) = applier_host.try_borrow_typed() else {
1081                return Ok(None);
1082            };
1083            let node = match applier.get_mut(node_id) {
1084                Ok(node) => node,
1085                Err(NodeError::Missing { .. }) => return Ok(None),
1086                Err(err) => return Err(err),
1087            };
1088            let any = node.as_any_mut();
1089            if let Some(subcompose) =
1090                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1091            {
1092                let handle = subcompose.handle();
1093                let resolved_modifiers = handle.resolved_modifiers();
1094                (handle, resolved_modifiers)
1095            } else {
1096                return Ok(None);
1097            }
1098        };
1099
1100        let runtime_handle = {
1101            let mut state = state_rc.borrow_mut();
1102            if state.runtime_handle.is_none() {
1103                // Try to borrow - if already borrowed, we can't get runtime handle
1104                if let Ok(applier) = applier_host.try_borrow_typed() {
1105                    state.runtime_handle = applier.runtime_handle();
1106                }
1107            }
1108            state
1109                .runtime_handle
1110                .clone()
1111                .ok_or(NodeError::MissingContext {
1112                    id: node_id,
1113                    reason: "runtime handle required for subcomposition",
1114                })?
1115        };
1116
1117        let props = resolved_modifiers.layout_properties();
1118        let padding = resolved_modifiers.padding();
1119        let offset = resolved_modifiers.offset();
1120        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
1121
1122        if let DimensionConstraint::Points(width) = props.width() {
1123            let constrained_width = width - padding.horizontal_sum();
1124            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
1125            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
1126        }
1127        if let DimensionConstraint::Points(height) = props.height() {
1128            let constrained_height = height - padding.vertical_sum();
1129            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
1130            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
1131        }
1132
1133        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
1134        let slots_host = slots_guard.host();
1135        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
1136        let observer = SnapshotStateObserver::new(|callback| callback());
1137        let composer = Composer::new(
1138            Rc::clone(&slots_host),
1139            applier_host_dyn,
1140            runtime_handle.clone(),
1141            observer,
1142            Some(node_id),
1143        );
1144        composer.enter_phase(Phase::Measure);
1145
1146        let state_rc_clone = Rc::clone(&state_rc);
1147        let measure_error = RefCell::new(None);
1148        let state_rc_for_subcompose = Rc::clone(&state_rc_clone);
1149        let error_for_subcompose = &measure_error;
1150        let measured_children: Rc<RefCell<HashMap<NodeId, Rc<MeasuredNode>>>> =
1151            Rc::new(RefCell::new(HashMap::default()));
1152        let measured_children_for_subcompose = Rc::clone(&measured_children);
1153
1154        let measure_result = node_handle.measure(
1155            &composer,
1156            node_id,
1157            inner_constraints,
1158            Box::new(
1159                move |child_id: NodeId, child_constraints: Constraints| -> Size {
1160                    match Self::measure_node(
1161                        Rc::clone(&state_rc_for_subcompose),
1162                        child_id,
1163                        child_constraints,
1164                    ) {
1165                        Ok(measured) => {
1166                            measured_children_for_subcompose
1167                                .borrow_mut()
1168                                .insert(child_id, Rc::clone(&measured));
1169                            measured.size
1170                        }
1171                        Err(err) => {
1172                            let mut slot = error_for_subcompose.borrow_mut();
1173                            if slot.is_none() {
1174                                *slot = Some(err);
1175                            }
1176                            Size::default()
1177                        }
1178                    }
1179                },
1180            ),
1181            &measure_error,
1182        )?;
1183        drop(composer);
1184        slots_guard.restore(slots_host.into_table()?);
1185
1186        if let Some(err) = measure_error.borrow_mut().take() {
1187            return Err(err);
1188        }
1189
1190        // NOTE: Children are now managed by the composer via insert_child commands
1191        // (from parent_stack initialization with root). set_active_children is no longer used.
1192
1193        let mut width = measure_result.size.width + padding.horizontal_sum();
1194        let mut height = measure_result.size.height + padding.vertical_sum();
1195
1196        width = resolve_dimension(
1197            width,
1198            props.width(),
1199            props.min_width(),
1200            props.max_width(),
1201            constraints.min_width,
1202            constraints.max_width,
1203        );
1204        height = resolve_dimension(
1205            height,
1206            props.height(),
1207            props.min_height(),
1208            props.max_height(),
1209            constraints.min_height,
1210            constraints.max_height,
1211        );
1212
1213        let mut children = Vec::with_capacity(measure_result.placements.len());
1214        let mut measured_children_by_id = measured_children.borrow_mut();
1215
1216        // Update the SubcomposeLayoutNode's size (position will be set by parent's placement)
1217        if let Ok(mut applier) = applier_host.try_borrow_typed() {
1218            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
1219                parent_node.set_measured_size(Size { width, height });
1220                parent_node.clear_needs_measure();
1221                parent_node.clear_needs_layout();
1222            });
1223        }
1224
1225        for placement in measure_result.placements {
1226            let child = if let Some(measured) = measured_children_by_id.remove(&placement.node_id) {
1227                measured
1228            } else {
1229                // Policies may place subcomposed children without calling `measure()` first
1230                // (for example, when they only need a slot's rendered content). Keep the
1231                // existing fallback for that case, but preserve the policy-time measurement
1232                // whenever it exists so we don't silently remeasure lazy items with the
1233                // container's tighter constraints.
1234                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?
1235            };
1236            let position = Point {
1237                x: padding.left + placement.x,
1238                y: padding.top + placement.y,
1239            };
1240
1241            // Critical: Update the child LayoutNode's retained state.
1242            // Standard layouts do this via Placeable::place(), but SubcomposeLayout logic
1243            // bypasses Placeables and returns raw Placements.
1244            if let Ok(mut applier) = applier_host.try_borrow_typed() {
1245                let _ = applier.with_node::<LayoutNode, _>(placement.node_id, |node| {
1246                    node.set_position(position);
1247                });
1248            }
1249
1250            children.push(MeasuredChild {
1251                node: child,
1252                offset: position,
1253            });
1254        }
1255
1256        // Update the SubcomposeLayoutNode's active children for rendering
1257        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1258
1259        Ok(Some(Rc::new(MeasuredNode::new(
1260            node_id,
1261            Size { width, height },
1262            offset,
1263            Point::default(), // Subcompose nodes: content_offset handled by child layout
1264            children,
1265        ))))
1266    }
1267    /// Measures through the layout modifier coordinator chain using reconciled modifier nodes.
1268    /// Iterates through LayoutModifierNode instances from the ModifierNodeChain and calls
1269    /// their measure() methods, mirroring Jetpack Compose's LayoutModifierNodeCoordinator pattern.
1270    ///
1271    /// Always succeeds, building a coordinator chain (possibly just InnerCoordinator) to measure.
1272    ///
1273    fn measure_through_modifier_chain(
1274        state_rc: &Rc<RefCell<Self>>,
1275        node_id: NodeId,
1276        measurables: &[Box<dyn Measurable>],
1277        measure_policy: &Rc<dyn MeasurePolicy>,
1278        constraints: Constraints,
1279        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1280    ) -> ModifierChainMeasurement {
1281        use cranpose_foundation::NodeCapabilities;
1282
1283        // Collect layout node information from the modifier chain
1284        layout_node_data.clear();
1285        let mut offset = Point::default();
1286
1287        {
1288            let state = state_rc.borrow();
1289            let mut applier = state.applier.borrow_typed();
1290
1291            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1292                let chain_handle = layout_node.modifier_chain();
1293
1294                if !chain_handle.has_layout_nodes() {
1295                    return;
1296                }
1297
1298                // Collect indices and node Rc clones for layout modifier nodes
1299                chain_handle.chain().for_each_forward_matching(
1300                    NodeCapabilities::LAYOUT,
1301                    |node_ref| {
1302                        if let Some(index) = node_ref.entry_index() {
1303                            // Get the Rc clone for this node
1304                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1305                                layout_node_data.push((index, Rc::clone(&node_rc)));
1306                            }
1307
1308                            // Extract offset from OffsetNode for the node's own position
1309                            // The coordinator chain handles placement_offset (for children),
1310                            // but the node's offset affects where IT is positioned in the parent
1311                            node_ref.with_node(|node| {
1312                                if let Some(offset_node) =
1313                                    node.as_any()
1314                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1315                                {
1316                                    let delta = offset_node.offset();
1317                                    offset.x += delta.x;
1318                                    offset.y += delta.y;
1319                                }
1320                            });
1321                        }
1322                    },
1323                );
1324            });
1325        }
1326
1327        // Fast path: if there are no layout modifiers, measure directly without coordinator chain.
1328        // This saves 3 allocations (shared_context, policy_result, InnerCoordinator box).
1329        if layout_node_data.is_empty() {
1330            let result = measure_policy.measure(measurables, constraints);
1331            let final_size = result.size;
1332            let placements = result.placements;
1333
1334            return ModifierChainMeasurement {
1335                result: MeasureResult {
1336                    size: final_size,
1337                    placements,
1338                },
1339                content_offset: Point::default(),
1340                offset,
1341            };
1342        }
1343
1344        // Slow path: build coordinator chain for layout modifiers.
1345        // Popping from the end preserves the "rightmost modifier measures first" order
1346        // without allocating or cloning the collected node list.
1347        // Create a shared context for this measurement pass to track invalidations
1348        let shared_context = Rc::new(RefCell::new(LayoutNodeContext::new()));
1349
1350        // Create the inner coordinator that wraps the measure policy
1351        let policy_result = Rc::new(RefCell::new(None));
1352        let inner_coordinator: Box<dyn NodeCoordinator + '_> =
1353            Box::new(coordinator::InnerCoordinator::new(
1354                Rc::clone(measure_policy),
1355                measurables,
1356                Rc::clone(&policy_result),
1357            ));
1358
1359        // Wrap each layout modifier node in a coordinator, building the chain
1360        let mut current_coordinator = inner_coordinator;
1361        while let Some((_, node_rc)) = layout_node_data.pop() {
1362            current_coordinator = Box::new(coordinator::LayoutModifierCoordinator::new(
1363                node_rc,
1364                current_coordinator,
1365                Rc::clone(&shared_context),
1366            ));
1367        }
1368
1369        // Measure through the complete coordinator chain
1370        let placeable = current_coordinator.measure(constraints);
1371        let final_size = Size {
1372            width: placeable.width(),
1373            height: placeable.height(),
1374        };
1375
1376        // Get accumulated content offset from the placeable (computed during measure)
1377        let content_offset = placeable.content_offset();
1378        let all_placement_offset = Point {
1379            x: content_offset.0,
1380            y: content_offset.1,
1381        };
1382
1383        // The content_offset for scroll/inner transforms is the accumulated placement offset
1384        // MINUS the node's own offset (which affects its position in the parent, not content position).
1385        // This properly separates: node position (offset) vs inner content position (content_offset).
1386        let content_offset = Point {
1387            x: all_placement_offset.x - offset.x,
1388            y: all_placement_offset.y - offset.y,
1389        };
1390
1391        // offset was already extracted from OffsetNode above
1392
1393        let placements = policy_result
1394            .borrow_mut()
1395            .take()
1396            .map(|result| result.placements)
1397            .unwrap_or_default();
1398
1399        // Process any invalidations requested during measurement
1400        let invalidations = shared_context.borrow_mut().take_invalidations();
1401        if !invalidations.is_empty() {
1402            // Mark the LayoutNode as needing the appropriate passes
1403            Self::with_applier_result(state_rc, |applier| {
1404                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1405                    for kind in invalidations {
1406                        match kind {
1407                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1408                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1409                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1410                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1411                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1412                        }
1413                    }
1414                })
1415            })
1416            .ok();
1417        }
1418
1419        ModifierChainMeasurement {
1420            result: MeasureResult {
1421                size: final_size,
1422                placements,
1423            },
1424            content_offset,
1425            offset,
1426        }
1427    }
1428
1429    fn measure_layout_node(
1430        state_rc: Rc<RefCell<Self>>,
1431        node_id: NodeId,
1432        snapshot: LayoutNodeSnapshot,
1433        constraints: Constraints,
1434    ) -> Result<Rc<MeasuredNode>, NodeError> {
1435        let cache_epoch = {
1436            let state = state_rc.borrow();
1437            state.cache_epoch
1438        };
1439        let LayoutNodeSnapshot {
1440            measure_policy,
1441            cache,
1442            needs_layout,
1443            needs_measure,
1444        } = snapshot;
1445        cache.activate(cache_epoch);
1446
1447        if needs_measure {
1448            // Node has needs_measure=true
1449        }
1450
1451        // Only check cache when the node is fully clean.
1452        // needs_layout=true means either the node itself or one of its descendants
1453        // must be revisited even if the node's own measured size can stay cached.
1454        if !needs_measure && !needs_layout {
1455            // Check cache for current constraints
1456            if let Some(cached) = cache.get_measurement(constraints) {
1457                // Clear dirty flag after successful cache hit
1458                Self::with_applier_result(&state_rc, |applier| {
1459                    applier.with_node::<LayoutNode, _>(node_id, |node| {
1460                        node.clear_needs_measure();
1461                        node.clear_needs_layout();
1462                    })
1463                })
1464                .ok();
1465                return Ok(cached);
1466            }
1467        }
1468
1469        let (runtime_handle, applier_host) = {
1470            let state = state_rc.borrow();
1471            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1472        };
1473
1474        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1475        let error = Rc::new(RefCell::new(None));
1476        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1477        let (measurables, records, child_ids, layout_node_data) = pools.parts();
1478
1479        applier_host
1480            .borrow_typed()
1481            .with_node::<LayoutNode, _>(node_id, |node| {
1482                child_ids.extend_from_slice(&node.children);
1483            })?;
1484
1485        for &child_id in child_ids.iter() {
1486            let measured = Rc::new(RefCell::new(None));
1487            let position = Rc::new(RefCell::new(None));
1488
1489            let data = {
1490                let mut applier = applier_host.borrow_typed();
1491                match applier.with_node::<LayoutNode, _>(child_id, |n| {
1492                    (
1493                        n.cache_handles(),
1494                        n.layout_state_handle(),
1495                        n.needs_layout(),
1496                        n.needs_measure(),
1497                    )
1498                }) {
1499                    Ok((cache, state, needs_layout, needs_measure)) => {
1500                        Some((cache, Some(state), needs_layout, needs_measure))
1501                    }
1502                    Err(NodeError::TypeMismatch { .. }) => {
1503                        match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |n| {
1504                            (n.needs_layout(), n.needs_measure())
1505                        }) {
1506                            Ok((needs_layout, needs_measure)) => Some((
1507                                LayoutNodeCacheHandles::default(),
1508                                None,
1509                                needs_layout,
1510                                needs_measure,
1511                            )),
1512                            Err(NodeError::TypeMismatch { .. }) => None,
1513                            Err(NodeError::Missing { .. }) => None,
1514                            Err(err) => return Err(err),
1515                        }
1516                    }
1517                    Err(NodeError::Missing { .. }) => None,
1518                    Err(err) => return Err(err),
1519                }
1520            };
1521
1522            let Some((cache_handles, layout_state, needs_layout, needs_measure)) = data else {
1523                continue;
1524            };
1525
1526            cache_handles.activate(cache_epoch);
1527
1528            records.push((
1529                child_id,
1530                ChildRecord {
1531                    measured: Rc::clone(&measured),
1532                    last_position: Rc::clone(&position),
1533                },
1534            ));
1535            measurables.push(Box::new(LayoutChildMeasurable::new(
1536                Rc::clone(&applier_host),
1537                child_id,
1538                measured,
1539                position,
1540                Rc::clone(&error),
1541                runtime_handle.clone(),
1542                cache_handles,
1543                cache_epoch,
1544                needs_layout || needs_measure,
1545                Some(measure_handle.clone()),
1546                layout_state,
1547            )));
1548        }
1549
1550        let chain_constraints = constraints;
1551
1552        let modifier_chain_result = Self::measure_through_modifier_chain(
1553            &state_rc,
1554            node_id,
1555            measurables.as_slice(),
1556            &measure_policy,
1557            chain_constraints,
1558            layout_node_data,
1559        );
1560
1561        // Modifier chain always succeeds - use the node-driven measurement.
1562        let (width, height, policy_result, content_offset, offset) = {
1563            let result = modifier_chain_result;
1564            // The size is already correct from the modifier chain (modifiers like SizeNode
1565            // have already enforced their constraints), so we use it directly.
1566            if let Some(err) = error.borrow_mut().take() {
1567                return Err(err);
1568            }
1569
1570            (
1571                result.result.size.width,
1572                result.result.size.height,
1573                result.result,
1574                result.content_offset,
1575                result.offset,
1576            )
1577        };
1578
1579        let mut measured_children = Vec::with_capacity(records.len());
1580        for (child_id, record) in records.iter() {
1581            if let Some(measured) = record.measured.borrow_mut().take() {
1582                let base_position = policy_result
1583                    .placements
1584                    .iter()
1585                    .find(|placement| placement.node_id == *child_id)
1586                    .map(|placement| Point {
1587                        x: placement.x,
1588                        y: placement.y,
1589                    })
1590                    .or_else(|| record.last_position.borrow().as_ref().copied())
1591                    .unwrap_or(Point { x: 0.0, y: 0.0 });
1592                // Apply content_offset (from scroll/transforms) to child positioning
1593                let position = Point {
1594                    x: content_offset.x + base_position.x,
1595                    y: content_offset.y + base_position.y,
1596                };
1597                measured_children.push(MeasuredChild {
1598                    node: measured,
1599                    offset: position,
1600                });
1601            }
1602        }
1603
1604        let measured = Rc::new(MeasuredNode::new(
1605            node_id,
1606            Size { width, height },
1607            offset,
1608            content_offset,
1609            measured_children,
1610        ));
1611
1612        cache.store_measurement(constraints, Rc::clone(&measured));
1613
1614        // Clear dirty flags and update derived state
1615        Self::with_applier_result(&state_rc, |applier| {
1616            applier.with_node::<LayoutNode, _>(node_id, |node| {
1617                node.clear_needs_measure();
1618                node.clear_needs_layout();
1619                node.set_measured_size(Size { width, height });
1620                node.set_content_offset(content_offset);
1621            })
1622        })
1623        .ok();
1624
1625        Ok(measured)
1626    }
1627}
1628
1629/// Snapshot of a LayoutNode's data for measuring.
1630/// This is a temporary copy used during the measure phase, not a live node.
1631///
1632/// Note: We capture `needs_measure` here because it's checked during measure to enable
1633/// selective measure optimization at the individual node level. Even if the tree is partially
1634/// dirty (some nodes changed), clean nodes can skip measure and use cached results.
1635struct LayoutNodeSnapshot {
1636    measure_policy: Rc<dyn MeasurePolicy>,
1637    cache: LayoutNodeCacheHandles,
1638    needs_layout: bool,
1639    /// Whether this specific node needs to be measured (vs using cached measurement)
1640    needs_measure: bool,
1641}
1642
1643impl LayoutNodeSnapshot {
1644    fn from_layout_node(node: &LayoutNode) -> Self {
1645        Self {
1646            measure_policy: Rc::clone(&node.measure_policy),
1647            cache: node.cache_handles(),
1648            needs_layout: node.needs_layout(),
1649            needs_measure: node.needs_measure(),
1650        }
1651    }
1652}
1653
1654// Helper types for accessing subsets of LayoutBuilderState
1655struct VecPools {
1656    state: Rc<RefCell<LayoutBuilderState>>,
1657    measurables: Option<Vec<Box<dyn Measurable>>>,
1658    records: Option<Vec<(NodeId, ChildRecord)>>,
1659    child_ids: Option<Vec<NodeId>>,
1660    layout_node_data: Option<Vec<LayoutModifierNodeData>>,
1661}
1662
1663impl VecPools {
1664    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1665        let (measurables, records, child_ids, layout_node_data) = {
1666            let mut state_mut = state.borrow_mut();
1667            (
1668                state_mut.tmp_measurables.acquire(),
1669                state_mut.tmp_records.acquire(),
1670                state_mut.tmp_child_ids.acquire(),
1671                state_mut.tmp_layout_node_data.acquire(),
1672            )
1673        };
1674        Self {
1675            state,
1676            measurables: Some(measurables),
1677            records: Some(records),
1678            child_ids: Some(child_ids),
1679            layout_node_data: Some(layout_node_data),
1680        }
1681    }
1682
1683    #[allow(clippy::type_complexity)] // Returns internal Vec references for layout operations
1684    fn parts(
1685        &mut self,
1686    ) -> (
1687        &mut Vec<Box<dyn Measurable>>,
1688        &mut Vec<(NodeId, ChildRecord)>,
1689        &mut Vec<NodeId>,
1690        &mut Vec<LayoutModifierNodeData>,
1691    ) {
1692        let measurables = self
1693            .measurables
1694            .as_mut()
1695            .expect("measurables already returned");
1696        let records = self.records.as_mut().expect("records already returned");
1697        let child_ids = self.child_ids.as_mut().expect("child_ids already returned");
1698        let layout_node_data = self
1699            .layout_node_data
1700            .as_mut()
1701            .expect("layout_node_data already returned");
1702        (measurables, records, child_ids, layout_node_data)
1703    }
1704}
1705
1706impl Drop for VecPools {
1707    fn drop(&mut self) {
1708        let mut state = self.state.borrow_mut();
1709        if let Some(measurables) = self.measurables.take() {
1710            state.tmp_measurables.release(measurables);
1711        }
1712        if let Some(records) = self.records.take() {
1713            state.tmp_records.release(records);
1714        }
1715        if let Some(child_ids) = self.child_ids.take() {
1716            state.tmp_child_ids.release(child_ids);
1717        }
1718        if let Some(layout_node_data) = self.layout_node_data.take() {
1719            state.tmp_layout_node_data.release(layout_node_data);
1720        }
1721    }
1722}
1723
1724struct SlotsGuard {
1725    state: Rc<RefCell<LayoutBuilderState>>,
1726    slots: Option<SlotTable>,
1727}
1728
1729impl SlotsGuard {
1730    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1731        let slots = {
1732            let state_ref = state.borrow();
1733            let mut slots_ref = state_ref.slots.borrow_mut();
1734            std::mem::take(&mut *slots_ref)
1735        };
1736        Self {
1737            state,
1738            slots: Some(slots),
1739        }
1740    }
1741
1742    fn host(&mut self) -> Rc<SlotsHost> {
1743        let slots = self.slots.take().unwrap_or_default();
1744        Rc::new(SlotsHost::new(slots))
1745    }
1746
1747    fn restore(&mut self, slots: SlotTable) {
1748        debug_assert!(self.slots.is_none());
1749        self.slots = Some(slots);
1750    }
1751}
1752
1753impl Drop for SlotsGuard {
1754    fn drop(&mut self) {
1755        if let Some(slots) = self.slots.take() {
1756            let state_ref = self.state.borrow();
1757            *state_ref.slots.borrow_mut() = slots;
1758        }
1759    }
1760}
1761
1762#[derive(Clone)]
1763struct LayoutMeasureHandle {
1764    state: Rc<RefCell<LayoutBuilderState>>,
1765}
1766
1767impl LayoutMeasureHandle {
1768    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1769        Self { state }
1770    }
1771
1772    fn measure(
1773        &self,
1774        node_id: NodeId,
1775        constraints: Constraints,
1776    ) -> Result<Rc<MeasuredNode>, NodeError> {
1777        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
1778    }
1779}
1780
1781#[derive(Debug, Clone)]
1782pub(crate) struct MeasuredNode {
1783    node_id: NodeId,
1784    size: Size,
1785    /// Node's position offset relative to parent (from OffsetNode etc.)
1786    offset: Point,
1787    /// Content offset for scroll/inner transforms (NOT node position)
1788    content_offset: Point,
1789    children: Vec<MeasuredChild>,
1790}
1791
1792impl MeasuredNode {
1793    fn new(
1794        node_id: NodeId,
1795        size: Size,
1796        offset: Point,
1797        content_offset: Point,
1798        children: Vec<MeasuredChild>,
1799    ) -> Self {
1800        Self {
1801            node_id,
1802            size,
1803            offset,
1804            content_offset,
1805            children,
1806        }
1807    }
1808}
1809
1810#[derive(Debug, Clone)]
1811struct MeasuredChild {
1812    node: Rc<MeasuredNode>,
1813    offset: Point,
1814}
1815
1816struct ChildRecord {
1817    measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1818    last_position: Rc<RefCell<Option<Point>>>,
1819}
1820
1821struct LayoutChildMeasurable {
1822    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1823    node_id: NodeId,
1824    measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1825    last_position: Rc<RefCell<Option<Point>>>,
1826    error: Rc<RefCell<Option<NodeError>>>,
1827    runtime_handle: Option<RuntimeHandle>,
1828    cache: LayoutNodeCacheHandles,
1829    cache_epoch: u64,
1830    force_remeasure: Cell<bool>,
1831    measure_handle: Option<LayoutMeasureHandle>,
1832    layout_state: Option<Rc<RefCell<crate::widgets::nodes::layout_node::LayoutState>>>,
1833}
1834
1835impl LayoutChildMeasurable {
1836    #[allow(clippy::too_many_arguments)] // Constructor needs all layout state for child measurement
1837    fn new(
1838        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1839        node_id: NodeId,
1840        measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1841        last_position: Rc<RefCell<Option<Point>>>,
1842        error: Rc<RefCell<Option<NodeError>>>,
1843        runtime_handle: Option<RuntimeHandle>,
1844        cache: LayoutNodeCacheHandles,
1845        cache_epoch: u64,
1846        force_remeasure: bool,
1847        measure_handle: Option<LayoutMeasureHandle>,
1848        layout_state: Option<Rc<RefCell<crate::widgets::nodes::layout_node::LayoutState>>>,
1849    ) -> Self {
1850        cache.activate(cache_epoch);
1851        Self {
1852            applier,
1853            node_id,
1854            measured,
1855            last_position,
1856            error,
1857            runtime_handle,
1858            cache,
1859            cache_epoch,
1860            force_remeasure: Cell::new(force_remeasure),
1861            measure_handle,
1862            layout_state,
1863        }
1864    }
1865
1866    fn record_error(&self, err: NodeError) {
1867        let mut slot = self.error.borrow_mut();
1868        if slot.is_none() {
1869            *slot = Some(err);
1870        }
1871    }
1872
1873    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
1874        if let Some(handle) = &self.measure_handle {
1875            handle.measure(self.node_id, constraints)
1876        } else {
1877            measure_node_with_host(
1878                Rc::clone(&self.applier),
1879                self.runtime_handle.clone(),
1880                self.node_id,
1881                constraints,
1882                self.cache_epoch,
1883            )
1884        }
1885    }
1886
1887    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
1888        self.cache.activate(self.cache_epoch);
1889        if !self.force_remeasure.get() {
1890            if let Some(cached) = self.cache.get_measurement(constraints) {
1891                return Some(cached);
1892            }
1893        }
1894
1895        match self.perform_measure(constraints) {
1896            Ok(measured) => {
1897                self.force_remeasure.set(false);
1898                self.cache
1899                    .store_measurement(constraints, Rc::clone(&measured));
1900                Some(measured)
1901            }
1902            Err(err) => {
1903                self.record_error(err);
1904                None
1905            }
1906        }
1907    }
1908}
1909
1910impl Measurable for LayoutChildMeasurable {
1911    fn measure(&self, constraints: Constraints) -> Placeable {
1912        self.cache.activate(self.cache_epoch);
1913        let measured_size;
1914        if !self.force_remeasure.get() {
1915            if let Some(cached) = self.cache.get_measurement(constraints) {
1916                measured_size = cached.size;
1917                *self.measured.borrow_mut() = Some(Rc::clone(&cached));
1918            } else {
1919                match self.perform_measure(constraints) {
1920                    Ok(measured) => {
1921                        self.force_remeasure.set(false);
1922                        measured_size = measured.size;
1923                        self.cache
1924                            .store_measurement(constraints, Rc::clone(&measured));
1925                        *self.measured.borrow_mut() = Some(measured);
1926                    }
1927                    Err(err) => {
1928                        self.record_error(err);
1929                        self.measured.borrow_mut().take();
1930                        measured_size = Size {
1931                            width: 0.0,
1932                            height: 0.0,
1933                        };
1934                    }
1935                }
1936            }
1937        } else {
1938            match self.perform_measure(constraints) {
1939                Ok(measured) => {
1940                    self.force_remeasure.set(false);
1941                    measured_size = measured.size;
1942                    self.cache
1943                        .store_measurement(constraints, Rc::clone(&measured));
1944                    *self.measured.borrow_mut() = Some(measured);
1945                }
1946                Err(err) => {
1947                    self.record_error(err);
1948                    self.measured.borrow_mut().take();
1949                    measured_size = Size {
1950                        width: 0.0,
1951                        height: 0.0,
1952                    };
1953                }
1954            }
1955        }
1956
1957        // Update retained LayoutNode state with measured size (new architecture).
1958        // PRIORITIZE direct handle to avoid Applier borrow conflicts during layout!
1959        if let Some(state) = &self.layout_state {
1960            let mut state = state.borrow_mut();
1961            state.size = measured_size;
1962            state.measurement_constraints = constraints;
1963        } else if let Ok(mut applier) = self.applier.try_borrow_typed() {
1964            let _ = applier.with_node::<LayoutNode, _>(self.node_id, |node| {
1965                node.set_measured_size(measured_size);
1966                node.set_measurement_constraints(constraints);
1967            });
1968        }
1969
1970        // Build the place closure that captures all state needed for placement
1971        let applier = Rc::clone(&self.applier);
1972        let node_id = self.node_id;
1973        let measured = Rc::clone(&self.measured);
1974        let last_position = Rc::clone(&self.last_position);
1975        let layout_state = self.layout_state.clone();
1976
1977        let place_fn = Rc::new(move |x: f32, y: f32| {
1978            // Retrieve the node's own offset (from modifiers like offset(), padding(), etc.)
1979            let internal_offset = measured
1980                .borrow()
1981                .as_ref()
1982                .map(|m| m.offset)
1983                .unwrap_or_default();
1984
1985            let position = Point {
1986                x: x + internal_offset.x,
1987                y: y + internal_offset.y,
1988            };
1989            *last_position.borrow_mut() = Some(position);
1990
1991            // Update retained LayoutNode state
1992            if let Some(state) = &layout_state {
1993                let mut state = state.borrow_mut();
1994                state.position = position;
1995                state.is_placed = true;
1996            } else if let Ok(mut applier) = applier.try_borrow_typed() {
1997                if applier
1998                    .with_node::<LayoutNode, _>(node_id, |node| {
1999                        node.set_position(position);
2000                    })
2001                    .is_err()
2002                {
2003                    let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2004                        node.set_position(position);
2005                    });
2006                }
2007            }
2008        });
2009
2010        Placeable::with_place_fn(
2011            measured_size.width,
2012            measured_size.height,
2013            self.node_id,
2014            place_fn,
2015        )
2016    }
2017
2018    fn min_intrinsic_width(&self, height: f32) -> f32 {
2019        let kind = IntrinsicKind::MinWidth(height);
2020        self.cache.activate(self.cache_epoch);
2021        if !self.force_remeasure.get() {
2022            if let Some(value) = self.cache.get_intrinsic(&kind) {
2023                return value;
2024            }
2025        }
2026        let constraints = Constraints {
2027            min_width: 0.0,
2028            max_width: f32::INFINITY,
2029            min_height: height,
2030            max_height: height,
2031        };
2032        if let Some(node) = self.intrinsic_measure(constraints) {
2033            let value = node.size.width;
2034            self.cache.store_intrinsic(kind, value);
2035            value
2036        } else {
2037            0.0
2038        }
2039    }
2040
2041    fn max_intrinsic_width(&self, height: f32) -> f32 {
2042        let kind = IntrinsicKind::MaxWidth(height);
2043        self.cache.activate(self.cache_epoch);
2044        if !self.force_remeasure.get() {
2045            if let Some(value) = self.cache.get_intrinsic(&kind) {
2046                return value;
2047            }
2048        }
2049        let constraints = Constraints {
2050            min_width: 0.0,
2051            max_width: f32::INFINITY,
2052            min_height: 0.0,
2053            max_height: height,
2054        };
2055        if let Some(node) = self.intrinsic_measure(constraints) {
2056            let value = node.size.width;
2057            self.cache.store_intrinsic(kind, value);
2058            value
2059        } else {
2060            0.0
2061        }
2062    }
2063
2064    fn min_intrinsic_height(&self, width: f32) -> f32 {
2065        let kind = IntrinsicKind::MinHeight(width);
2066        self.cache.activate(self.cache_epoch);
2067        if !self.force_remeasure.get() {
2068            if let Some(value) = self.cache.get_intrinsic(&kind) {
2069                return value;
2070            }
2071        }
2072        let constraints = Constraints {
2073            min_width: width,
2074            max_width: width,
2075            min_height: 0.0,
2076            max_height: f32::INFINITY,
2077        };
2078        if let Some(node) = self.intrinsic_measure(constraints) {
2079            let value = node.size.height;
2080            self.cache.store_intrinsic(kind, value);
2081            value
2082        } else {
2083            0.0
2084        }
2085    }
2086
2087    fn max_intrinsic_height(&self, width: f32) -> f32 {
2088        let kind = IntrinsicKind::MaxHeight(width);
2089        self.cache.activate(self.cache_epoch);
2090        if !self.force_remeasure.get() {
2091            if let Some(value) = self.cache.get_intrinsic(&kind) {
2092                return value;
2093            }
2094        }
2095        let constraints = Constraints {
2096            min_width: 0.0,
2097            max_width: width,
2098            min_height: 0.0,
2099            max_height: f32::INFINITY,
2100        };
2101        if let Some(node) = self.intrinsic_measure(constraints) {
2102            let value = node.size.height;
2103            self.cache.store_intrinsic(kind, value);
2104            value
2105        } else {
2106            0.0
2107        }
2108    }
2109
2110    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
2111        // Try to borrow the applier - if it's already borrowed (nested measurement), return None.
2112        // This is safe because parent data doesn't change during measurement.
2113        let Ok(mut applier) = self.applier.try_borrow_typed() else {
2114            return None;
2115        };
2116
2117        applier
2118            .with_node::<LayoutNode, _>(self.node_id, |layout_node| {
2119                let props = layout_node.resolved_modifiers().layout_properties();
2120                props.weight().map(|weight_data| {
2121                    cranpose_ui_layout::FlexParentData::new(weight_data.weight, weight_data.fill)
2122                })
2123            })
2124            .ok()
2125            .flatten()
2126    }
2127}
2128
2129fn measure_node_with_host(
2130    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2131    runtime_handle: Option<RuntimeHandle>,
2132    node_id: NodeId,
2133    constraints: Constraints,
2134    epoch: u64,
2135) -> Result<Rc<MeasuredNode>, NodeError> {
2136    let runtime_handle = match runtime_handle {
2137        Some(handle) => Some(handle),
2138        None => applier.borrow_typed().runtime_handle(),
2139    };
2140    let mut builder =
2141        LayoutBuilder::new_with_epoch(applier, epoch, Rc::new(RefCell::new(SlotTable::default())));
2142    builder.set_runtime_handle(runtime_handle);
2143    builder.measure_node(node_id, constraints)
2144}
2145
2146#[derive(Clone)]
2147struct RuntimeNodeMetadata {
2148    modifier: Modifier,
2149    resolved_modifiers: ResolvedModifiers,
2150    modifier_slices: Rc<ModifierNodeSlices>,
2151    role: SemanticsRole,
2152    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
2153}
2154
2155impl Default for RuntimeNodeMetadata {
2156    fn default() -> Self {
2157        Self {
2158            modifier: Modifier::empty(),
2159            resolved_modifiers: ResolvedModifiers::default(),
2160            modifier_slices: Rc::default(),
2161            role: SemanticsRole::Unknown,
2162            button_handler: None,
2163        }
2164    }
2165}
2166
2167fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
2168    modifier_slices
2169        .text_content()
2170        .map(|text| SemanticsRole::Text {
2171            value: text.to_string(),
2172        })
2173        .unwrap_or(SemanticsRole::Layout)
2174}
2175
2176fn runtime_metadata_for(
2177    applier: &mut MemoryApplier,
2178    node_id: NodeId,
2179) -> Result<RuntimeNodeMetadata, NodeError> {
2180    // Try LayoutNode (the primary modern path)
2181    // IMPORTANT: We use with_node (reference) instead of try_clone because cloning
2182    // LayoutNode creates a NEW ModifierChainHandle with NEW nodes and NEW handlers,
2183    // which would lose gesture state like press_position.
2184    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
2185        let modifier = layout.modifier.clone();
2186        let resolved_modifiers = layout.resolved_modifiers();
2187        let modifier_slices = layout.modifier_slices_snapshot();
2188        let role = role_from_modifier_slices(&modifier_slices);
2189
2190        RuntimeNodeMetadata {
2191            modifier,
2192            resolved_modifiers,
2193            modifier_slices,
2194            role,
2195            button_handler: None,
2196        }
2197    }) {
2198        return Ok(meta);
2199    }
2200
2201    // Try SubcomposeLayoutNode
2202    if let Ok((modifier, resolved_modifiers, modifier_slices)) = applier
2203        .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2204            (
2205                node.modifier(),
2206                node.resolved_modifiers(),
2207                node.modifier_slices_snapshot(),
2208            )
2209        })
2210    {
2211        return Ok(RuntimeNodeMetadata {
2212            modifier,
2213            resolved_modifiers,
2214            modifier_slices,
2215            role: SemanticsRole::Subcompose,
2216            button_handler: None,
2217        });
2218    }
2219    Ok(RuntimeNodeMetadata::default())
2220}
2221
2222fn clear_semantics_dirty_flags(
2223    applier: &mut MemoryApplier,
2224    node: &MeasuredNode,
2225) -> Result<(), NodeError> {
2226    match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
2227        layout.clear_needs_semantics();
2228    }) {
2229        Ok(()) => {}
2230        Err(NodeError::Missing { .. }) => {}
2231        Err(NodeError::TypeMismatch { .. }) => {
2232            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
2233                subcompose.clear_needs_semantics();
2234            }) {
2235                Ok(()) | Err(NodeError::Missing { .. }) | Err(NodeError::TypeMismatch { .. }) => {}
2236                Err(err) => return Err(err),
2237            }
2238        }
2239        Err(err) => return Err(err),
2240    }
2241
2242    for child in &node.children {
2243        clear_semantics_dirty_flags(applier, &child.node)?;
2244    }
2245
2246    Ok(())
2247}
2248
2249fn build_semantics_tree_from_live_nodes(
2250    applier: &mut MemoryApplier,
2251    node: &MeasuredNode,
2252) -> Result<SemanticsTree, NodeError> {
2253    Ok(SemanticsTree::new(build_semantics_node_from_live_nodes(
2254        applier, node,
2255    )?))
2256}
2257
2258fn semantics_node_from_parts(
2259    node_id: NodeId,
2260    mut role: SemanticsRole,
2261    config: Option<SemanticsConfiguration>,
2262    children: Vec<SemanticsNode>,
2263) -> SemanticsNode {
2264    let mut actions = Vec::new();
2265    let mut description = None;
2266
2267    if let Some(config) = config {
2268        if config.is_button {
2269            role = SemanticsRole::Button;
2270        }
2271        if config.is_clickable {
2272            actions.push(SemanticsAction::Click {
2273                handler: SemanticsCallback::new(node_id),
2274            });
2275        }
2276        description = config.content_description;
2277    }
2278
2279    SemanticsNode::new(node_id, role, actions, children, description)
2280}
2281
2282fn build_semantics_node_from_live_nodes(
2283    applier: &mut MemoryApplier,
2284    node: &MeasuredNode,
2285) -> Result<SemanticsNode, NodeError> {
2286    let (role, config) = match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
2287        let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
2288        let config = layout.semantics_configuration();
2289        layout.clear_needs_semantics();
2290        (role, config)
2291    }) {
2292        Ok(data) => data,
2293        Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
2294            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
2295                subcompose.clear_needs_semantics();
2296                (
2297                    SemanticsRole::Subcompose,
2298                    collect_semantics_from_modifier(&subcompose.modifier()),
2299                )
2300            }) {
2301                Ok(data) => data,
2302                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
2303                    (SemanticsRole::Unknown, None)
2304                }
2305                Err(err) => return Err(err),
2306            }
2307        }
2308        Err(err) => return Err(err),
2309    };
2310
2311    let mut children = Vec::with_capacity(node.children.len());
2312    for child in &node.children {
2313        children.push(build_semantics_node_from_live_nodes(applier, &child.node)?);
2314    }
2315
2316    Ok(semantics_node_from_parts(
2317        node.node_id,
2318        role,
2319        config,
2320        children,
2321    ))
2322}
2323
2324fn record_semantics_allocation_stats(node: &SemanticsNode, stats: &mut LayoutAllocationDebugStats) {
2325    stats.semantics_node_count += 1;
2326    stats.semantics_action_count += node.actions.len();
2327    stats.semantics_action_capacity += node.actions.capacity();
2328    stats.semantics_child_count += node.children.len();
2329    stats.semantics_child_capacity += node.children.capacity();
2330    stats.semantics_heap_bytes += node.actions.capacity() * size_of::<SemanticsAction>();
2331    stats.semantics_heap_bytes += node.children.capacity() * size_of::<SemanticsNode>();
2332
2333    if let Some(description) = &node.description {
2334        stats.semantics_description_count += 1;
2335        stats.semantics_description_bytes += description.capacity();
2336        stats.semantics_heap_bytes += description.capacity();
2337    }
2338    if let SemanticsRole::Text { value } = &node.role {
2339        stats.semantics_text_role_bytes += value.capacity();
2340        stats.semantics_heap_bytes += value.capacity();
2341    }
2342
2343    for child in &node.children {
2344        record_semantics_allocation_stats(child, stats);
2345    }
2346}
2347
2348fn record_layout_box_allocation_stats(
2349    layout_box: &LayoutBox,
2350    stats: &mut LayoutAllocationDebugStats,
2351) {
2352    stats.layout_box_count += 1;
2353    stats.layout_box_child_count += layout_box.children.len();
2354    stats.layout_box_child_capacity += layout_box.children.capacity();
2355    stats.layout_box_heap_bytes += layout_box.children.capacity() * size_of::<LayoutBox>();
2356    stats.add_modifier_slice(layout_box.node_data.modifier_slices().debug_stats());
2357
2358    for child in &layout_box.children {
2359        record_layout_box_allocation_stats(child, stats);
2360    }
2361}
2362
2363fn build_layout_tree(
2364    applier: &mut MemoryApplier,
2365    node: &MeasuredNode,
2366) -> Result<LayoutTree, NodeError> {
2367    fn place(
2368        applier: &mut MemoryApplier,
2369        node: &MeasuredNode,
2370        origin: Point,
2371    ) -> Result<LayoutBox, NodeError> {
2372        // Include the node's own offset (from OffsetNode) in its position
2373        let top_left = Point {
2374            x: origin.x + node.offset.x,
2375            y: origin.y + node.offset.y,
2376        };
2377        let rect = GeometryRect {
2378            x: top_left.x,
2379            y: top_left.y,
2380            width: node.size.width,
2381            height: node.size.height,
2382        };
2383        let info = runtime_metadata_for(applier, node.node_id)?;
2384        let kind = layout_kind_from_metadata(node.node_id, &info);
2385        let RuntimeNodeMetadata {
2386            modifier,
2387            resolved_modifiers,
2388            modifier_slices,
2389            ..
2390        } = info;
2391        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
2392        let mut children = Vec::with_capacity(node.children.len());
2393        for child in &node.children {
2394            let child_origin = Point {
2395                x: top_left.x + child.offset.x,
2396                y: top_left.y + child.offset.y,
2397            };
2398            children.push(place(applier, &child.node, child_origin)?);
2399        }
2400        Ok(LayoutBox::new(
2401            node.node_id,
2402            rect,
2403            node.content_offset,
2404            data,
2405            children,
2406        ))
2407    }
2408
2409    Ok(LayoutTree::new(place(
2410        applier,
2411        node,
2412        Point { x: 0.0, y: 0.0 },
2413    )?))
2414}
2415
2416fn semantics_role_from_layout_box(layout_box: &LayoutBox) -> SemanticsRole {
2417    match &layout_box.node_data.kind {
2418        LayoutNodeKind::Subcompose => SemanticsRole::Subcompose,
2419        LayoutNodeKind::Spacer => SemanticsRole::Spacer,
2420        LayoutNodeKind::Unknown => SemanticsRole::Unknown,
2421        LayoutNodeKind::Button { .. } => SemanticsRole::Button,
2422        LayoutNodeKind::Layout => layout_box
2423            .node_data
2424            .modifier_slices()
2425            .text_content()
2426            .map(|text| SemanticsRole::Text {
2427                value: text.to_string(),
2428            })
2429            .unwrap_or(SemanticsRole::Layout),
2430    }
2431}
2432
2433fn build_semantics_node_from_layout_box(layout_box: &LayoutBox) -> SemanticsNode {
2434    let children = layout_box
2435        .children
2436        .iter()
2437        .map(build_semantics_node_from_layout_box)
2438        .collect();
2439
2440    semantics_node_from_parts(
2441        layout_box.node_id,
2442        semantics_role_from_layout_box(layout_box),
2443        collect_semantics_from_modifier(&layout_box.node_data.modifier),
2444        children,
2445    )
2446}
2447
2448fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
2449    match &info.role {
2450        SemanticsRole::Layout => LayoutNodeKind::Layout,
2451        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
2452        SemanticsRole::Text { .. } => {
2453            // Text content is now handled via TextModifierNode in the modifier chain
2454            // and collected in modifier_slices.text_content(). LayoutNodeKind should
2455            // reflect the layout policy (EmptyMeasurePolicy), not the content type.
2456            LayoutNodeKind::Layout
2457        }
2458        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
2459        SemanticsRole::Button => {
2460            let handler = info
2461                .button_handler
2462                .as_ref()
2463                .cloned()
2464                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
2465            LayoutNodeKind::Button { on_click: handler }
2466        }
2467        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
2468    }
2469}
2470
2471fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
2472    let horizontal = padding.horizontal_sum();
2473    let vertical = padding.vertical_sum();
2474    let min_width = (constraints.min_width - horizontal).max(0.0);
2475    let mut max_width = constraints.max_width;
2476    if max_width.is_finite() {
2477        max_width = (max_width - horizontal).max(0.0);
2478    }
2479    let min_height = (constraints.min_height - vertical).max(0.0);
2480    let mut max_height = constraints.max_height;
2481    if max_height.is_finite() {
2482        max_height = (max_height - vertical).max(0.0);
2483    }
2484    normalize_constraints(Constraints {
2485        min_width,
2486        max_width,
2487        min_height,
2488        max_height,
2489    })
2490}
2491
2492#[cfg(test)]
2493pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
2494    match alignment {
2495        HorizontalAlignment::Start => 0.0,
2496        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
2497        HorizontalAlignment::End => (available - child).max(0.0),
2498    }
2499}
2500
2501#[cfg(test)]
2502pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
2503    match alignment {
2504        VerticalAlignment::Top => 0.0,
2505        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
2506        VerticalAlignment::Bottom => (available - child).max(0.0),
2507    }
2508}
2509
2510fn resolve_dimension(
2511    base: f32,
2512    explicit: DimensionConstraint,
2513    min_override: Option<f32>,
2514    max_override: Option<f32>,
2515    min_limit: f32,
2516    max_limit: f32,
2517) -> f32 {
2518    let mut min_bound = min_limit;
2519    if let Some(min_value) = min_override {
2520        min_bound = min_bound.max(min_value);
2521    }
2522
2523    let mut max_bound = if max_limit.is_finite() {
2524        max_limit
2525    } else {
2526        max_override.unwrap_or(max_limit)
2527    };
2528    if let Some(max_value) = max_override {
2529        if max_bound.is_finite() {
2530            max_bound = max_bound.min(max_value);
2531        } else {
2532            max_bound = max_value;
2533        }
2534    }
2535    if max_bound < min_bound {
2536        max_bound = min_bound;
2537    }
2538
2539    let mut size = match explicit {
2540        DimensionConstraint::Points(points) => points,
2541        DimensionConstraint::Fraction(fraction) => {
2542            if max_limit.is_finite() {
2543                max_limit * fraction.clamp(0.0, 1.0)
2544            } else {
2545                base
2546            }
2547        }
2548        DimensionConstraint::Unspecified => base,
2549        // Intrinsic sizing is resolved at a higher level where we have access to children.
2550        // At this point we just use the base size as a fallback.
2551        DimensionConstraint::Intrinsic(_) => base,
2552    };
2553
2554    size = clamp_dimension(size, min_bound, max_bound);
2555    size = clamp_dimension(size, min_limit, max_limit);
2556    size.max(0.0)
2557}
2558
2559fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
2560    let mut result = value.max(min);
2561    if max.is_finite() {
2562        result = result.min(max);
2563    }
2564    result
2565}
2566
2567fn normalize_constraints(mut constraints: Constraints) -> Constraints {
2568    if constraints.max_width < constraints.min_width {
2569        constraints.max_width = constraints.min_width;
2570    }
2571    if constraints.max_height < constraints.min_height {
2572        constraints.max_height = constraints.min_height;
2573    }
2574    constraints
2575}
2576
2577#[cfg(test)]
2578#[path = "tests/layout_tests.rs"]
2579mod tests;