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    for node_id in crate::render_state::take_modifier_slice_repass_nodes() {
893        if let Ok(node) = applier.get_mut(node_id) {
894            let any = node.as_any_mut();
895            if let Some(layout) = any.downcast_mut::<crate::widgets::nodes::LayoutNode>() {
896                layout.mark_modifier_slices_dirty();
897            } else if let Some(subcompose) =
898                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
899            {
900                subcompose.mark_modifier_slices_dirty();
901            }
902        }
903    }
904    let repass_nodes = crate::take_layout_repass_nodes();
905    if repass_nodes.is_empty() {
906        return Ok(());
907    }
908    for node_id in repass_nodes {
909        cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
910    }
911    applier.get_mut(root)?.mark_needs_layout();
912    Ok(())
913}
914
915struct LayoutBuilder {
916    state: Rc<RefCell<LayoutBuilderState>>,
917}
918
919impl LayoutBuilder {
920    fn new_with_epoch(
921        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
922        epoch: u64,
923        slots: Rc<RefCell<SlotTable>>,
924    ) -> Self {
925        Self {
926            state: Rc::new(RefCell::new(LayoutBuilderState::new_with_epoch(
927                applier, epoch, slots,
928            ))),
929        }
930    }
931
932    fn measure_node(
933        &mut self,
934        node_id: NodeId,
935        constraints: Constraints,
936    ) -> Result<Rc<MeasuredNode>, NodeError> {
937        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
938    }
939
940    fn set_runtime_handle(&mut self, handle: Option<RuntimeHandle>) {
941        self.state.borrow_mut().runtime_handle = handle;
942    }
943}
944
945struct LayoutBuilderState {
946    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
947    runtime_handle: Option<RuntimeHandle>,
948    /// Shared handle to the slot table. This is shared with ApplierSlotGuard
949    /// to ensure panic-safety: even if we panic, the guard can restore slots.
950    slots: Rc<RefCell<SlotTable>>,
951    cache_epoch: u64,
952    tmp_measurables: ScratchVecPool<Box<dyn Measurable>>,
953    tmp_records: ScratchVecPool<(NodeId, ChildRecord)>,
954    tmp_child_ids: ScratchVecPool<NodeId>,
955    tmp_layout_node_data: ScratchVecPool<LayoutModifierNodeData>,
956}
957
958impl LayoutBuilderState {
959    fn new_with_epoch(
960        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
961        epoch: u64,
962        slots: Rc<RefCell<SlotTable>>,
963    ) -> Self {
964        let runtime_handle = applier.borrow_typed().runtime_handle();
965
966        Self {
967            applier,
968            runtime_handle,
969            slots,
970            cache_epoch: epoch,
971            tmp_measurables: ScratchVecPool::default(),
972            tmp_records: ScratchVecPool::default(),
973            tmp_child_ids: ScratchVecPool::default(),
974            tmp_layout_node_data: ScratchVecPool::default(),
975        }
976    }
977
978    fn try_with_applier_result<R>(
979        state_rc: &Rc<RefCell<Self>>,
980        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
981    ) -> Option<Result<R, NodeError>> {
982        let host = {
983            let state = state_rc.borrow();
984            Rc::clone(&state.applier)
985        };
986
987        // Try to borrow - if already borrowed (nested call), return None
988        let Ok(mut applier) = host.try_borrow_typed() else {
989            return None;
990        };
991
992        Some(f(&mut applier))
993    }
994
995    fn with_applier_result<R>(
996        state_rc: &Rc<RefCell<Self>>,
997        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
998    ) -> Result<R, NodeError> {
999        Self::try_with_applier_result(state_rc, f).unwrap_or_else(|| {
1000            Err(NodeError::MissingContext {
1001                id: NodeId::default(),
1002                reason: "applier already borrowed",
1003            })
1004        })
1005    }
1006
1007    /// Clears the is_placed flag for a node at the start of measurement.
1008    /// This ensures nodes that drop out of placement won't render with stale geometry.
1009    fn clear_node_placed(state_rc: &Rc<RefCell<Self>>, node_id: NodeId) {
1010        let host = {
1011            let state = state_rc.borrow();
1012            Rc::clone(&state.applier)
1013        };
1014        let Ok(mut applier) = host.try_borrow_typed() else {
1015            return;
1016        };
1017        // Try LayoutNode first, then SubcomposeLayoutNode
1018        if applier
1019            .with_node::<LayoutNode, _>(node_id, |node| {
1020                node.clear_placed();
1021            })
1022            .is_err()
1023        {
1024            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1025                node.clear_placed();
1026            });
1027        }
1028    }
1029
1030    fn measure_node(
1031        state_rc: Rc<RefCell<Self>>,
1032        node_id: NodeId,
1033        constraints: Constraints,
1034    ) -> Result<Rc<MeasuredNode>, NodeError> {
1035        // Clear is_placed at the start of measurement.
1036        // Nodes that are placed will have is_placed set to true via Placeable::place().
1037        // Nodes that drop out of placement (not placed this pass) will remain is_placed=false.
1038        Self::clear_node_placed(&state_rc, node_id);
1039
1040        // Try SubcomposeLayoutNode first
1041        if let Some(subcompose) =
1042            Self::try_measure_subcompose(Rc::clone(&state_rc), node_id, constraints)?
1043        {
1044            return Ok(subcompose);
1045        }
1046
1047        // Try LayoutNode (the primary modern path)
1048        if let Some(result) = Self::try_with_applier_result(&state_rc, |applier| {
1049            match applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1050                LayoutNodeSnapshot::from_layout_node(layout_node)
1051            }) {
1052                Ok(snapshot) => Ok(Some(snapshot)),
1053                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
1054                Err(err) => Err(err),
1055            }
1056        }) {
1057            // Applier was available, process the result
1058            if let Some(snapshot) = result? {
1059                return Self::measure_layout_node(
1060                    Rc::clone(&state_rc),
1061                    node_id,
1062                    snapshot,
1063                    constraints,
1064                );
1065            }
1066        }
1067        // If applier was busy (None) or snapshot was None, fall through to fallback
1068
1069        // No alternate fallbacks - all widgets use LayoutNode or SubcomposeLayoutNode
1070        // If we reach here, it's an unknown node type (shouldn't happen in normal use)
1071        Ok(Rc::new(MeasuredNode::new(
1072            node_id,
1073            Size::default(),
1074            Point { x: 0.0, y: 0.0 },
1075            Point::default(), // No content offset for fallback nodes
1076            Vec::new(),
1077        )))
1078    }
1079
1080    fn try_measure_subcompose(
1081        state_rc: Rc<RefCell<Self>>,
1082        node_id: NodeId,
1083        constraints: Constraints,
1084    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1085        let applier_host = {
1086            let state = state_rc.borrow();
1087            Rc::clone(&state.applier)
1088        };
1089
1090        let (node_handle, resolved_modifiers) = {
1091            // Try to borrow - if already borrowed (nested measurement), return None
1092            let Ok(mut applier) = applier_host.try_borrow_typed() else {
1093                return Ok(None);
1094            };
1095            let node = match applier.get_mut(node_id) {
1096                Ok(node) => node,
1097                Err(NodeError::Missing { .. }) => return Ok(None),
1098                Err(err) => return Err(err),
1099            };
1100            let any = node.as_any_mut();
1101            if let Some(subcompose) =
1102                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1103            {
1104                let handle = subcompose.handle();
1105                let resolved_modifiers = handle.resolved_modifiers();
1106                (handle, resolved_modifiers)
1107            } else {
1108                return Ok(None);
1109            }
1110        };
1111
1112        let runtime_handle = {
1113            let mut state = state_rc.borrow_mut();
1114            if state.runtime_handle.is_none() {
1115                // Try to borrow - if already borrowed, we can't get runtime handle
1116                if let Ok(applier) = applier_host.try_borrow_typed() {
1117                    state.runtime_handle = applier.runtime_handle();
1118                }
1119            }
1120            state
1121                .runtime_handle
1122                .clone()
1123                .ok_or(NodeError::MissingContext {
1124                    id: node_id,
1125                    reason: "runtime handle required for subcomposition",
1126                })?
1127        };
1128
1129        let props = resolved_modifiers.layout_properties();
1130        let padding = resolved_modifiers.padding();
1131        let offset = resolved_modifiers.offset();
1132        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
1133
1134        if let DimensionConstraint::Points(width) = props.width() {
1135            let constrained_width = width - padding.horizontal_sum();
1136            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
1137            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
1138        }
1139        if let DimensionConstraint::Points(height) = props.height() {
1140            let constrained_height = height - padding.vertical_sum();
1141            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
1142            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
1143        }
1144
1145        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
1146        let slots_host = slots_guard.host();
1147        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
1148        let observer = SnapshotStateObserver::new(|callback| callback());
1149        let composer = Composer::new(
1150            Rc::clone(&slots_host),
1151            applier_host_dyn,
1152            runtime_handle.clone(),
1153            observer,
1154            Some(node_id),
1155        );
1156        composer.enter_phase(Phase::Measure);
1157
1158        let state_rc_clone = Rc::clone(&state_rc);
1159        let measure_error = RefCell::new(None);
1160        let state_rc_for_subcompose = Rc::clone(&state_rc_clone);
1161        let error_for_subcompose = &measure_error;
1162        let measured_children: Rc<RefCell<HashMap<NodeId, Rc<MeasuredNode>>>> =
1163            Rc::new(RefCell::new(HashMap::default()));
1164        let measured_children_for_subcompose = Rc::clone(&measured_children);
1165
1166        let measure_result = node_handle.measure(
1167            &composer,
1168            node_id,
1169            inner_constraints,
1170            Box::new(
1171                move |child_id: NodeId, child_constraints: Constraints| -> Size {
1172                    match Self::measure_node(
1173                        Rc::clone(&state_rc_for_subcompose),
1174                        child_id,
1175                        child_constraints,
1176                    ) {
1177                        Ok(measured) => {
1178                            measured_children_for_subcompose
1179                                .borrow_mut()
1180                                .insert(child_id, Rc::clone(&measured));
1181                            measured.size
1182                        }
1183                        Err(err) => {
1184                            let mut slot = error_for_subcompose.borrow_mut();
1185                            if slot.is_none() {
1186                                *slot = Some(err);
1187                            }
1188                            Size::default()
1189                        }
1190                    }
1191                },
1192            ),
1193            &measure_error,
1194        )?;
1195        drop(composer);
1196        slots_guard.restore(slots_host.into_table()?);
1197
1198        if let Some(err) = measure_error.borrow_mut().take() {
1199            return Err(err);
1200        }
1201
1202        // NOTE: Children are now managed by the composer via insert_child commands
1203        // (from parent_stack initialization with root). set_active_children is no longer used.
1204
1205        let mut width = measure_result.size.width + padding.horizontal_sum();
1206        let mut height = measure_result.size.height + padding.vertical_sum();
1207
1208        width = resolve_dimension(
1209            width,
1210            props.width(),
1211            props.min_width(),
1212            props.max_width(),
1213            constraints.min_width,
1214            constraints.max_width,
1215        );
1216        height = resolve_dimension(
1217            height,
1218            props.height(),
1219            props.min_height(),
1220            props.max_height(),
1221            constraints.min_height,
1222            constraints.max_height,
1223        );
1224
1225        let mut children = Vec::with_capacity(measure_result.placements.len());
1226        let mut measured_children_by_id = measured_children.borrow_mut();
1227
1228        // Update the SubcomposeLayoutNode's size (position will be set by parent's placement)
1229        if let Ok(mut applier) = applier_host.try_borrow_typed() {
1230            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
1231                parent_node.set_measured_size(Size { width, height });
1232                parent_node.clear_needs_measure();
1233                parent_node.clear_needs_layout();
1234            });
1235        }
1236
1237        for placement in measure_result.placements {
1238            let child = if let Some(measured) = measured_children_by_id.remove(&placement.node_id) {
1239                measured
1240            } else {
1241                // Policies may place subcomposed children without calling `measure()` first
1242                // (for example, when they only need a slot's rendered content). Keep the
1243                // existing fallback for that case, but preserve the policy-time measurement
1244                // whenever it exists so we don't silently remeasure lazy items with the
1245                // container's tighter constraints.
1246                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?
1247            };
1248            let position = Point {
1249                x: padding.left + placement.x,
1250                y: padding.top + placement.y,
1251            };
1252
1253            // Critical: Update the child LayoutNode's retained state.
1254            // Standard layouts do this via Placeable::place(), but SubcomposeLayout logic
1255            // bypasses Placeables and returns raw Placements.
1256            if let Ok(mut applier) = applier_host.try_borrow_typed() {
1257                let _ = applier.with_node::<LayoutNode, _>(placement.node_id, |node| {
1258                    node.set_position(position);
1259                });
1260            }
1261
1262            children.push(MeasuredChild {
1263                node: child,
1264                offset: position,
1265            });
1266        }
1267
1268        // Update the SubcomposeLayoutNode's active children for rendering
1269        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1270
1271        Ok(Some(Rc::new(MeasuredNode::new(
1272            node_id,
1273            Size { width, height },
1274            offset,
1275            Point::default(), // Subcompose nodes: content_offset handled by child layout
1276            children,
1277        ))))
1278    }
1279    /// Measures through the layout modifier coordinator chain using reconciled modifier nodes.
1280    /// Iterates through LayoutModifierNode instances from the ModifierNodeChain and calls
1281    /// their measure() methods, mirroring Jetpack Compose's LayoutModifierNodeCoordinator pattern.
1282    ///
1283    /// Always succeeds, building a coordinator chain (possibly just InnerCoordinator) to measure.
1284    ///
1285    fn measure_through_modifier_chain(
1286        state_rc: &Rc<RefCell<Self>>,
1287        node_id: NodeId,
1288        measurables: &[Box<dyn Measurable>],
1289        measure_policy: &Rc<dyn MeasurePolicy>,
1290        constraints: Constraints,
1291        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1292    ) -> ModifierChainMeasurement {
1293        use cranpose_foundation::NodeCapabilities;
1294
1295        // Collect layout node information from the modifier chain
1296        layout_node_data.clear();
1297        let mut offset = Point::default();
1298
1299        {
1300            let state = state_rc.borrow();
1301            let mut applier = state.applier.borrow_typed();
1302
1303            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1304                let chain_handle = layout_node.modifier_chain();
1305
1306                if !chain_handle.has_layout_nodes() {
1307                    return;
1308                }
1309
1310                // Collect indices and node Rc clones for layout modifier nodes
1311                chain_handle.chain().for_each_forward_matching(
1312                    NodeCapabilities::LAYOUT,
1313                    |node_ref| {
1314                        if let Some(index) = node_ref.entry_index() {
1315                            // Get the Rc clone for this node
1316                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1317                                layout_node_data.push((index, Rc::clone(&node_rc)));
1318                            }
1319
1320                            // Extract offset from OffsetNode for the node's own position
1321                            // The coordinator chain handles placement_offset (for children),
1322                            // but the node's offset affects where IT is positioned in the parent
1323                            node_ref.with_node(|node| {
1324                                if let Some(offset_node) =
1325                                    node.as_any()
1326                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1327                                {
1328                                    let delta = offset_node.offset();
1329                                    offset.x += delta.x;
1330                                    offset.y += delta.y;
1331                                }
1332                            });
1333                        }
1334                    },
1335                );
1336            });
1337        }
1338
1339        // Fast path: if there are no layout modifiers, measure directly without coordinator chain.
1340        // This saves 3 allocations (shared_context, policy_result, InnerCoordinator box).
1341        if layout_node_data.is_empty() {
1342            let result = measure_policy.measure(measurables, constraints);
1343            let final_size = result.size;
1344            let placements = result.placements;
1345
1346            return ModifierChainMeasurement {
1347                result: MeasureResult {
1348                    size: final_size,
1349                    placements,
1350                },
1351                content_offset: Point::default(),
1352                offset,
1353            };
1354        }
1355
1356        // Slow path: build coordinator chain for layout modifiers.
1357        // Popping from the end preserves the "rightmost modifier measures first" order
1358        // without allocating or cloning the collected node list.
1359        // Create a shared context for this measurement pass to track invalidations
1360        let shared_context = Rc::new(RefCell::new(LayoutNodeContext::new()));
1361
1362        // Create the inner coordinator that wraps the measure policy
1363        let policy_result = Rc::new(RefCell::new(None));
1364        let inner_coordinator: Box<dyn NodeCoordinator + '_> =
1365            Box::new(coordinator::InnerCoordinator::new(
1366                Rc::clone(measure_policy),
1367                measurables,
1368                Rc::clone(&policy_result),
1369            ));
1370
1371        // Wrap each layout modifier node in a coordinator, building the chain
1372        let mut current_coordinator = inner_coordinator;
1373        while let Some((_, node_rc)) = layout_node_data.pop() {
1374            current_coordinator = Box::new(coordinator::LayoutModifierCoordinator::new(
1375                node_rc,
1376                current_coordinator,
1377                Rc::clone(&shared_context),
1378            ));
1379        }
1380
1381        // Measure through the complete coordinator chain
1382        let placeable = current_coordinator.measure(constraints);
1383        let final_size = Size {
1384            width: placeable.width(),
1385            height: placeable.height(),
1386        };
1387
1388        // Get accumulated content offset from the placeable (computed during measure)
1389        let content_offset = placeable.content_offset();
1390        let all_placement_offset = Point {
1391            x: content_offset.0,
1392            y: content_offset.1,
1393        };
1394
1395        // The content_offset for scroll/inner transforms is the accumulated placement offset
1396        // MINUS the node's own offset (which affects its position in the parent, not content position).
1397        // This properly separates: node position (offset) vs inner content position (content_offset).
1398        let content_offset = Point {
1399            x: all_placement_offset.x - offset.x,
1400            y: all_placement_offset.y - offset.y,
1401        };
1402
1403        // offset was already extracted from OffsetNode above
1404
1405        let placements = policy_result
1406            .borrow_mut()
1407            .take()
1408            .map(|result| result.placements)
1409            .unwrap_or_default();
1410
1411        // Process any invalidations requested during measurement
1412        let invalidations = shared_context.borrow_mut().take_invalidations();
1413        if !invalidations.is_empty() {
1414            // Mark the LayoutNode as needing the appropriate passes
1415            Self::with_applier_result(state_rc, |applier| {
1416                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1417                    for kind in invalidations {
1418                        match kind {
1419                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1420                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1421                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1422                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1423                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1424                        }
1425                    }
1426                })
1427            })
1428            .ok();
1429        }
1430
1431        ModifierChainMeasurement {
1432            result: MeasureResult {
1433                size: final_size,
1434                placements,
1435            },
1436            content_offset,
1437            offset,
1438        }
1439    }
1440
1441    fn measure_layout_node(
1442        state_rc: Rc<RefCell<Self>>,
1443        node_id: NodeId,
1444        snapshot: LayoutNodeSnapshot,
1445        constraints: Constraints,
1446    ) -> Result<Rc<MeasuredNode>, NodeError> {
1447        let cache_epoch = {
1448            let state = state_rc.borrow();
1449            state.cache_epoch
1450        };
1451        let LayoutNodeSnapshot {
1452            measure_policy,
1453            cache,
1454            needs_layout,
1455            needs_measure,
1456        } = snapshot;
1457        cache.activate(cache_epoch);
1458
1459        if needs_measure {
1460            // Node has needs_measure=true
1461        }
1462
1463        // Only check cache when the node is fully clean.
1464        // needs_layout=true means either the node itself or one of its descendants
1465        // must be revisited even if the node's own measured size can stay cached.
1466        if !needs_measure && !needs_layout {
1467            // Check cache for current constraints
1468            if let Some(cached) = cache.get_measurement(constraints) {
1469                // Clear dirty flag after successful cache hit
1470                Self::with_applier_result(&state_rc, |applier| {
1471                    applier.with_node::<LayoutNode, _>(node_id, |node| {
1472                        node.clear_needs_measure();
1473                        node.clear_needs_layout();
1474                    })
1475                })
1476                .ok();
1477                return Ok(cached);
1478            }
1479        }
1480
1481        let (runtime_handle, applier_host) = {
1482            let state = state_rc.borrow();
1483            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1484        };
1485
1486        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1487        let error = Rc::new(RefCell::new(None));
1488        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1489        let (measurables, records, child_ids, layout_node_data) = pools.parts();
1490
1491        applier_host
1492            .borrow_typed()
1493            .with_node::<LayoutNode, _>(node_id, |node| {
1494                child_ids.extend_from_slice(&node.children);
1495            })?;
1496
1497        for &child_id in child_ids.iter() {
1498            let measured = Rc::new(RefCell::new(None));
1499            let position = Rc::new(RefCell::new(None));
1500
1501            let data = {
1502                let mut applier = applier_host.borrow_typed();
1503                match applier.with_node::<LayoutNode, _>(child_id, |n| {
1504                    (
1505                        n.cache_handles(),
1506                        n.layout_state_handle(),
1507                        n.needs_layout(),
1508                        n.needs_measure(),
1509                    )
1510                }) {
1511                    Ok((cache, state, needs_layout, needs_measure)) => {
1512                        Some((cache, Some(state), needs_layout, needs_measure))
1513                    }
1514                    Err(NodeError::TypeMismatch { .. }) => {
1515                        match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |n| {
1516                            (n.needs_layout(), n.needs_measure())
1517                        }) {
1518                            Ok((needs_layout, needs_measure)) => Some((
1519                                LayoutNodeCacheHandles::default(),
1520                                None,
1521                                needs_layout,
1522                                needs_measure,
1523                            )),
1524                            Err(NodeError::TypeMismatch { .. }) => None,
1525                            Err(NodeError::Missing { .. }) => None,
1526                            Err(err) => return Err(err),
1527                        }
1528                    }
1529                    Err(NodeError::Missing { .. }) => None,
1530                    Err(err) => return Err(err),
1531                }
1532            };
1533
1534            let Some((cache_handles, layout_state, needs_layout, needs_measure)) = data else {
1535                continue;
1536            };
1537
1538            cache_handles.activate(cache_epoch);
1539
1540            records.push((
1541                child_id,
1542                ChildRecord {
1543                    measured: Rc::clone(&measured),
1544                    last_position: Rc::clone(&position),
1545                },
1546            ));
1547            measurables.push(Box::new(LayoutChildMeasurable::new(
1548                Rc::clone(&applier_host),
1549                child_id,
1550                measured,
1551                position,
1552                Rc::clone(&error),
1553                runtime_handle.clone(),
1554                cache_handles,
1555                cache_epoch,
1556                needs_layout || needs_measure,
1557                Some(measure_handle.clone()),
1558                layout_state,
1559            )));
1560        }
1561
1562        let chain_constraints = constraints;
1563
1564        let modifier_chain_result = Self::measure_through_modifier_chain(
1565            &state_rc,
1566            node_id,
1567            measurables.as_slice(),
1568            &measure_policy,
1569            chain_constraints,
1570            layout_node_data,
1571        );
1572
1573        // Modifier chain always succeeds - use the node-driven measurement.
1574        let (width, height, policy_result, content_offset, offset) = {
1575            let result = modifier_chain_result;
1576            // The size is already correct from the modifier chain (modifiers like SizeNode
1577            // have already enforced their constraints), so we use it directly.
1578            if let Some(err) = error.borrow_mut().take() {
1579                return Err(err);
1580            }
1581
1582            (
1583                result.result.size.width,
1584                result.result.size.height,
1585                result.result,
1586                result.content_offset,
1587                result.offset,
1588            )
1589        };
1590
1591        let mut measured_children = Vec::with_capacity(records.len());
1592        for (child_id, record) in records.iter() {
1593            if let Some(measured) = record.measured.borrow_mut().take() {
1594                let base_position = policy_result
1595                    .placements
1596                    .iter()
1597                    .find(|placement| placement.node_id == *child_id)
1598                    .map(|placement| Point {
1599                        x: placement.x,
1600                        y: placement.y,
1601                    })
1602                    .or_else(|| record.last_position.borrow().as_ref().copied())
1603                    .unwrap_or(Point { x: 0.0, y: 0.0 });
1604                // Apply content_offset (from scroll/transforms) to child positioning
1605                let position = Point {
1606                    x: content_offset.x + base_position.x,
1607                    y: content_offset.y + base_position.y,
1608                };
1609                measured_children.push(MeasuredChild {
1610                    node: measured,
1611                    offset: position,
1612                });
1613            }
1614        }
1615
1616        let measured = Rc::new(MeasuredNode::new(
1617            node_id,
1618            Size { width, height },
1619            offset,
1620            content_offset,
1621            measured_children,
1622        ));
1623
1624        cache.store_measurement(constraints, Rc::clone(&measured));
1625
1626        // Clear dirty flags and update derived state
1627        Self::with_applier_result(&state_rc, |applier| {
1628            applier.with_node::<LayoutNode, _>(node_id, |node| {
1629                node.clear_needs_measure();
1630                node.clear_needs_layout();
1631                node.set_measured_size(Size { width, height });
1632                node.set_content_offset(content_offset);
1633            })
1634        })
1635        .ok();
1636
1637        Ok(measured)
1638    }
1639}
1640
1641/// Snapshot of a LayoutNode's data for measuring.
1642/// This is a temporary copy used during the measure phase, not a live node.
1643///
1644/// Note: We capture `needs_measure` here because it's checked during measure to enable
1645/// selective measure optimization at the individual node level. Even if the tree is partially
1646/// dirty (some nodes changed), clean nodes can skip measure and use cached results.
1647struct LayoutNodeSnapshot {
1648    measure_policy: Rc<dyn MeasurePolicy>,
1649    cache: LayoutNodeCacheHandles,
1650    needs_layout: bool,
1651    /// Whether this specific node needs to be measured (vs using cached measurement)
1652    needs_measure: bool,
1653}
1654
1655impl LayoutNodeSnapshot {
1656    fn from_layout_node(node: &LayoutNode) -> Self {
1657        Self {
1658            measure_policy: Rc::clone(&node.measure_policy),
1659            cache: node.cache_handles(),
1660            needs_layout: node.needs_layout(),
1661            needs_measure: node.needs_measure(),
1662        }
1663    }
1664}
1665
1666// Helper types for accessing subsets of LayoutBuilderState
1667struct VecPools {
1668    state: Rc<RefCell<LayoutBuilderState>>,
1669    measurables: Option<Vec<Box<dyn Measurable>>>,
1670    records: Option<Vec<(NodeId, ChildRecord)>>,
1671    child_ids: Option<Vec<NodeId>>,
1672    layout_node_data: Option<Vec<LayoutModifierNodeData>>,
1673}
1674
1675impl VecPools {
1676    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1677        let (measurables, records, child_ids, layout_node_data) = {
1678            let mut state_mut = state.borrow_mut();
1679            (
1680                state_mut.tmp_measurables.acquire(),
1681                state_mut.tmp_records.acquire(),
1682                state_mut.tmp_child_ids.acquire(),
1683                state_mut.tmp_layout_node_data.acquire(),
1684            )
1685        };
1686        Self {
1687            state,
1688            measurables: Some(measurables),
1689            records: Some(records),
1690            child_ids: Some(child_ids),
1691            layout_node_data: Some(layout_node_data),
1692        }
1693    }
1694
1695    #[allow(clippy::type_complexity)] // Returns internal Vec references for layout operations
1696    fn parts(
1697        &mut self,
1698    ) -> (
1699        &mut Vec<Box<dyn Measurable>>,
1700        &mut Vec<(NodeId, ChildRecord)>,
1701        &mut Vec<NodeId>,
1702        &mut Vec<LayoutModifierNodeData>,
1703    ) {
1704        let measurables = self
1705            .measurables
1706            .as_mut()
1707            .expect("measurables already returned");
1708        let records = self.records.as_mut().expect("records already returned");
1709        let child_ids = self.child_ids.as_mut().expect("child_ids already returned");
1710        let layout_node_data = self
1711            .layout_node_data
1712            .as_mut()
1713            .expect("layout_node_data already returned");
1714        (measurables, records, child_ids, layout_node_data)
1715    }
1716}
1717
1718impl Drop for VecPools {
1719    fn drop(&mut self) {
1720        let mut state = self.state.borrow_mut();
1721        if let Some(measurables) = self.measurables.take() {
1722            state.tmp_measurables.release(measurables);
1723        }
1724        if let Some(records) = self.records.take() {
1725            state.tmp_records.release(records);
1726        }
1727        if let Some(child_ids) = self.child_ids.take() {
1728            state.tmp_child_ids.release(child_ids);
1729        }
1730        if let Some(layout_node_data) = self.layout_node_data.take() {
1731            state.tmp_layout_node_data.release(layout_node_data);
1732        }
1733    }
1734}
1735
1736struct SlotsGuard {
1737    state: Rc<RefCell<LayoutBuilderState>>,
1738    slots: Option<SlotTable>,
1739}
1740
1741impl SlotsGuard {
1742    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1743        let slots = {
1744            let state_ref = state.borrow();
1745            let mut slots_ref = state_ref.slots.borrow_mut();
1746            std::mem::take(&mut *slots_ref)
1747        };
1748        Self {
1749            state,
1750            slots: Some(slots),
1751        }
1752    }
1753
1754    fn host(&mut self) -> Rc<SlotsHost> {
1755        let slots = self.slots.take().unwrap_or_default();
1756        Rc::new(SlotsHost::new(slots))
1757    }
1758
1759    fn restore(&mut self, slots: SlotTable) {
1760        debug_assert!(self.slots.is_none());
1761        self.slots = Some(slots);
1762    }
1763}
1764
1765impl Drop for SlotsGuard {
1766    fn drop(&mut self) {
1767        if let Some(slots) = self.slots.take() {
1768            let state_ref = self.state.borrow();
1769            *state_ref.slots.borrow_mut() = slots;
1770        }
1771    }
1772}
1773
1774#[derive(Clone)]
1775struct LayoutMeasureHandle {
1776    state: Rc<RefCell<LayoutBuilderState>>,
1777}
1778
1779impl LayoutMeasureHandle {
1780    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
1781        Self { state }
1782    }
1783
1784    fn measure(
1785        &self,
1786        node_id: NodeId,
1787        constraints: Constraints,
1788    ) -> Result<Rc<MeasuredNode>, NodeError> {
1789        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
1790    }
1791}
1792
1793#[derive(Debug, Clone)]
1794pub(crate) struct MeasuredNode {
1795    node_id: NodeId,
1796    size: Size,
1797    /// Node's position offset relative to parent (from OffsetNode etc.)
1798    offset: Point,
1799    /// Content offset for scroll/inner transforms (NOT node position)
1800    content_offset: Point,
1801    children: Vec<MeasuredChild>,
1802}
1803
1804impl MeasuredNode {
1805    fn new(
1806        node_id: NodeId,
1807        size: Size,
1808        offset: Point,
1809        content_offset: Point,
1810        children: Vec<MeasuredChild>,
1811    ) -> Self {
1812        Self {
1813            node_id,
1814            size,
1815            offset,
1816            content_offset,
1817            children,
1818        }
1819    }
1820}
1821
1822#[derive(Debug, Clone)]
1823struct MeasuredChild {
1824    node: Rc<MeasuredNode>,
1825    offset: Point,
1826}
1827
1828struct ChildRecord {
1829    measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1830    last_position: Rc<RefCell<Option<Point>>>,
1831}
1832
1833struct LayoutChildMeasurable {
1834    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1835    node_id: NodeId,
1836    measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1837    last_position: Rc<RefCell<Option<Point>>>,
1838    error: Rc<RefCell<Option<NodeError>>>,
1839    runtime_handle: Option<RuntimeHandle>,
1840    cache: LayoutNodeCacheHandles,
1841    cache_epoch: u64,
1842    force_remeasure: Cell<bool>,
1843    measure_handle: Option<LayoutMeasureHandle>,
1844    layout_state: Option<Rc<RefCell<crate::widgets::nodes::layout_node::LayoutState>>>,
1845}
1846
1847impl LayoutChildMeasurable {
1848    #[allow(clippy::too_many_arguments)] // Constructor needs all layout state for child measurement
1849    fn new(
1850        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1851        node_id: NodeId,
1852        measured: Rc<RefCell<Option<Rc<MeasuredNode>>>>,
1853        last_position: Rc<RefCell<Option<Point>>>,
1854        error: Rc<RefCell<Option<NodeError>>>,
1855        runtime_handle: Option<RuntimeHandle>,
1856        cache: LayoutNodeCacheHandles,
1857        cache_epoch: u64,
1858        force_remeasure: bool,
1859        measure_handle: Option<LayoutMeasureHandle>,
1860        layout_state: Option<Rc<RefCell<crate::widgets::nodes::layout_node::LayoutState>>>,
1861    ) -> Self {
1862        cache.activate(cache_epoch);
1863        Self {
1864            applier,
1865            node_id,
1866            measured,
1867            last_position,
1868            error,
1869            runtime_handle,
1870            cache,
1871            cache_epoch,
1872            force_remeasure: Cell::new(force_remeasure),
1873            measure_handle,
1874            layout_state,
1875        }
1876    }
1877
1878    fn record_error(&self, err: NodeError) {
1879        let mut slot = self.error.borrow_mut();
1880        if slot.is_none() {
1881            *slot = Some(err);
1882        }
1883    }
1884
1885    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
1886        if let Some(handle) = &self.measure_handle {
1887            handle.measure(self.node_id, constraints)
1888        } else {
1889            measure_node_with_host(
1890                Rc::clone(&self.applier),
1891                self.runtime_handle.clone(),
1892                self.node_id,
1893                constraints,
1894                self.cache_epoch,
1895            )
1896        }
1897    }
1898
1899    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
1900        self.cache.activate(self.cache_epoch);
1901        if !self.force_remeasure.get() {
1902            if let Some(cached) = self.cache.get_measurement(constraints) {
1903                return Some(cached);
1904            }
1905        }
1906
1907        match self.perform_measure(constraints) {
1908            Ok(measured) => {
1909                self.force_remeasure.set(false);
1910                self.cache
1911                    .store_measurement(constraints, Rc::clone(&measured));
1912                Some(measured)
1913            }
1914            Err(err) => {
1915                self.record_error(err);
1916                None
1917            }
1918        }
1919    }
1920}
1921
1922impl Measurable for LayoutChildMeasurable {
1923    fn measure(&self, constraints: Constraints) -> Placeable {
1924        self.cache.activate(self.cache_epoch);
1925        let measured_size;
1926        if !self.force_remeasure.get() {
1927            if let Some(cached) = self.cache.get_measurement(constraints) {
1928                measured_size = cached.size;
1929                *self.measured.borrow_mut() = Some(Rc::clone(&cached));
1930            } else {
1931                match self.perform_measure(constraints) {
1932                    Ok(measured) => {
1933                        self.force_remeasure.set(false);
1934                        measured_size = measured.size;
1935                        self.cache
1936                            .store_measurement(constraints, Rc::clone(&measured));
1937                        *self.measured.borrow_mut() = Some(measured);
1938                    }
1939                    Err(err) => {
1940                        self.record_error(err);
1941                        self.measured.borrow_mut().take();
1942                        measured_size = Size {
1943                            width: 0.0,
1944                            height: 0.0,
1945                        };
1946                    }
1947                }
1948            }
1949        } else {
1950            match self.perform_measure(constraints) {
1951                Ok(measured) => {
1952                    self.force_remeasure.set(false);
1953                    measured_size = measured.size;
1954                    self.cache
1955                        .store_measurement(constraints, Rc::clone(&measured));
1956                    *self.measured.borrow_mut() = Some(measured);
1957                }
1958                Err(err) => {
1959                    self.record_error(err);
1960                    self.measured.borrow_mut().take();
1961                    measured_size = Size {
1962                        width: 0.0,
1963                        height: 0.0,
1964                    };
1965                }
1966            }
1967        }
1968
1969        // Update retained LayoutNode state with measured size (new architecture).
1970        // PRIORITIZE direct handle to avoid Applier borrow conflicts during layout!
1971        if let Some(state) = &self.layout_state {
1972            let mut state = state.borrow_mut();
1973            state.size = measured_size;
1974            state.measurement_constraints = constraints;
1975        } else if let Ok(mut applier) = self.applier.try_borrow_typed() {
1976            let _ = applier.with_node::<LayoutNode, _>(self.node_id, |node| {
1977                node.set_measured_size(measured_size);
1978                node.set_measurement_constraints(constraints);
1979            });
1980        }
1981
1982        // Build the place closure that captures all state needed for placement
1983        let applier = Rc::clone(&self.applier);
1984        let node_id = self.node_id;
1985        let measured = Rc::clone(&self.measured);
1986        let last_position = Rc::clone(&self.last_position);
1987        let layout_state = self.layout_state.clone();
1988
1989        let place_fn = Rc::new(move |x: f32, y: f32| {
1990            // Retrieve the node's own offset (from modifiers like offset(), padding(), etc.)
1991            let internal_offset = measured
1992                .borrow()
1993                .as_ref()
1994                .map(|m| m.offset)
1995                .unwrap_or_default();
1996
1997            let position = Point {
1998                x: x + internal_offset.x,
1999                y: y + internal_offset.y,
2000            };
2001            *last_position.borrow_mut() = Some(position);
2002
2003            // Update retained LayoutNode state
2004            if let Some(state) = &layout_state {
2005                let mut state = state.borrow_mut();
2006                state.position = position;
2007                state.is_placed = true;
2008            } else if let Ok(mut applier) = applier.try_borrow_typed() {
2009                if applier
2010                    .with_node::<LayoutNode, _>(node_id, |node| {
2011                        node.set_position(position);
2012                    })
2013                    .is_err()
2014                {
2015                    let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2016                        node.set_position(position);
2017                    });
2018                }
2019            }
2020        });
2021
2022        Placeable::with_place_fn(
2023            measured_size.width,
2024            measured_size.height,
2025            self.node_id,
2026            place_fn,
2027        )
2028    }
2029
2030    fn min_intrinsic_width(&self, height: f32) -> f32 {
2031        let kind = IntrinsicKind::MinWidth(height);
2032        self.cache.activate(self.cache_epoch);
2033        if !self.force_remeasure.get() {
2034            if let Some(value) = self.cache.get_intrinsic(&kind) {
2035                return value;
2036            }
2037        }
2038        let constraints = Constraints {
2039            min_width: 0.0,
2040            max_width: f32::INFINITY,
2041            min_height: height,
2042            max_height: height,
2043        };
2044        if let Some(node) = self.intrinsic_measure(constraints) {
2045            let value = node.size.width;
2046            self.cache.store_intrinsic(kind, value);
2047            value
2048        } else {
2049            0.0
2050        }
2051    }
2052
2053    fn max_intrinsic_width(&self, height: f32) -> f32 {
2054        let kind = IntrinsicKind::MaxWidth(height);
2055        self.cache.activate(self.cache_epoch);
2056        if !self.force_remeasure.get() {
2057            if let Some(value) = self.cache.get_intrinsic(&kind) {
2058                return value;
2059            }
2060        }
2061        let constraints = Constraints {
2062            min_width: 0.0,
2063            max_width: f32::INFINITY,
2064            min_height: 0.0,
2065            max_height: height,
2066        };
2067        if let Some(node) = self.intrinsic_measure(constraints) {
2068            let value = node.size.width;
2069            self.cache.store_intrinsic(kind, value);
2070            value
2071        } else {
2072            0.0
2073        }
2074    }
2075
2076    fn min_intrinsic_height(&self, width: f32) -> f32 {
2077        let kind = IntrinsicKind::MinHeight(width);
2078        self.cache.activate(self.cache_epoch);
2079        if !self.force_remeasure.get() {
2080            if let Some(value) = self.cache.get_intrinsic(&kind) {
2081                return value;
2082            }
2083        }
2084        let constraints = Constraints {
2085            min_width: width,
2086            max_width: width,
2087            min_height: 0.0,
2088            max_height: f32::INFINITY,
2089        };
2090        if let Some(node) = self.intrinsic_measure(constraints) {
2091            let value = node.size.height;
2092            self.cache.store_intrinsic(kind, value);
2093            value
2094        } else {
2095            0.0
2096        }
2097    }
2098
2099    fn max_intrinsic_height(&self, width: f32) -> f32 {
2100        let kind = IntrinsicKind::MaxHeight(width);
2101        self.cache.activate(self.cache_epoch);
2102        if !self.force_remeasure.get() {
2103            if let Some(value) = self.cache.get_intrinsic(&kind) {
2104                return value;
2105            }
2106        }
2107        let constraints = Constraints {
2108            min_width: 0.0,
2109            max_width: width,
2110            min_height: 0.0,
2111            max_height: f32::INFINITY,
2112        };
2113        if let Some(node) = self.intrinsic_measure(constraints) {
2114            let value = node.size.height;
2115            self.cache.store_intrinsic(kind, value);
2116            value
2117        } else {
2118            0.0
2119        }
2120    }
2121
2122    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
2123        // Try to borrow the applier - if it's already borrowed (nested measurement), return None.
2124        // This is safe because parent data doesn't change during measurement.
2125        let Ok(mut applier) = self.applier.try_borrow_typed() else {
2126            return None;
2127        };
2128
2129        applier
2130            .with_node::<LayoutNode, _>(self.node_id, |layout_node| {
2131                let props = layout_node.resolved_modifiers().layout_properties();
2132                props.weight().map(|weight_data| {
2133                    cranpose_ui_layout::FlexParentData::new(weight_data.weight, weight_data.fill)
2134                })
2135            })
2136            .ok()
2137            .flatten()
2138    }
2139}
2140
2141fn measure_node_with_host(
2142    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2143    runtime_handle: Option<RuntimeHandle>,
2144    node_id: NodeId,
2145    constraints: Constraints,
2146    epoch: u64,
2147) -> Result<Rc<MeasuredNode>, NodeError> {
2148    let runtime_handle = match runtime_handle {
2149        Some(handle) => Some(handle),
2150        None => applier.borrow_typed().runtime_handle(),
2151    };
2152    let mut builder =
2153        LayoutBuilder::new_with_epoch(applier, epoch, Rc::new(RefCell::new(SlotTable::default())));
2154    builder.set_runtime_handle(runtime_handle);
2155    builder.measure_node(node_id, constraints)
2156}
2157
2158#[derive(Clone)]
2159struct RuntimeNodeMetadata {
2160    modifier: Modifier,
2161    resolved_modifiers: ResolvedModifiers,
2162    modifier_slices: Rc<ModifierNodeSlices>,
2163    role: SemanticsRole,
2164    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
2165}
2166
2167impl Default for RuntimeNodeMetadata {
2168    fn default() -> Self {
2169        Self {
2170            modifier: Modifier::empty(),
2171            resolved_modifiers: ResolvedModifiers::default(),
2172            modifier_slices: Rc::default(),
2173            role: SemanticsRole::Unknown,
2174            button_handler: None,
2175        }
2176    }
2177}
2178
2179fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
2180    modifier_slices
2181        .text_content()
2182        .map(|text| SemanticsRole::Text {
2183            value: text.to_string(),
2184        })
2185        .unwrap_or(SemanticsRole::Layout)
2186}
2187
2188fn runtime_metadata_for(
2189    applier: &mut MemoryApplier,
2190    node_id: NodeId,
2191) -> Result<RuntimeNodeMetadata, NodeError> {
2192    // Try LayoutNode (the primary modern path)
2193    // IMPORTANT: We use with_node (reference) instead of try_clone because cloning
2194    // LayoutNode creates a NEW ModifierChainHandle with NEW nodes and NEW handlers,
2195    // which would lose gesture state like press_position.
2196    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
2197        let modifier = layout.modifier.clone();
2198        let resolved_modifiers = layout.resolved_modifiers();
2199        let modifier_slices = layout.modifier_slices_snapshot();
2200        let role = role_from_modifier_slices(&modifier_slices);
2201
2202        RuntimeNodeMetadata {
2203            modifier,
2204            resolved_modifiers,
2205            modifier_slices,
2206            role,
2207            button_handler: None,
2208        }
2209    }) {
2210        return Ok(meta);
2211    }
2212
2213    // Try SubcomposeLayoutNode
2214    if let Ok((modifier, resolved_modifiers, modifier_slices)) = applier
2215        .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2216            (
2217                node.modifier(),
2218                node.resolved_modifiers(),
2219                node.modifier_slices_snapshot(),
2220            )
2221        })
2222    {
2223        return Ok(RuntimeNodeMetadata {
2224            modifier,
2225            resolved_modifiers,
2226            modifier_slices,
2227            role: SemanticsRole::Subcompose,
2228            button_handler: None,
2229        });
2230    }
2231    Ok(RuntimeNodeMetadata::default())
2232}
2233
2234fn clear_semantics_dirty_flags(
2235    applier: &mut MemoryApplier,
2236    node: &MeasuredNode,
2237) -> Result<(), NodeError> {
2238    match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
2239        layout.clear_needs_semantics();
2240    }) {
2241        Ok(()) => {}
2242        Err(NodeError::Missing { .. }) => {}
2243        Err(NodeError::TypeMismatch { .. }) => {
2244            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
2245                subcompose.clear_needs_semantics();
2246            }) {
2247                Ok(()) | Err(NodeError::Missing { .. }) | Err(NodeError::TypeMismatch { .. }) => {}
2248                Err(err) => return Err(err),
2249            }
2250        }
2251        Err(err) => return Err(err),
2252    }
2253
2254    for child in &node.children {
2255        clear_semantics_dirty_flags(applier, &child.node)?;
2256    }
2257
2258    Ok(())
2259}
2260
2261fn build_semantics_tree_from_live_nodes(
2262    applier: &mut MemoryApplier,
2263    node: &MeasuredNode,
2264) -> Result<SemanticsTree, NodeError> {
2265    Ok(SemanticsTree::new(build_semantics_node_from_live_nodes(
2266        applier, node,
2267    )?))
2268}
2269
2270fn semantics_node_from_parts(
2271    node_id: NodeId,
2272    mut role: SemanticsRole,
2273    config: Option<SemanticsConfiguration>,
2274    children: Vec<SemanticsNode>,
2275) -> SemanticsNode {
2276    let mut actions = Vec::new();
2277    let mut description = None;
2278
2279    if let Some(config) = config {
2280        if config.is_button {
2281            role = SemanticsRole::Button;
2282        }
2283        if config.is_clickable {
2284            actions.push(SemanticsAction::Click {
2285                handler: SemanticsCallback::new(node_id),
2286            });
2287        }
2288        description = config.content_description;
2289    }
2290
2291    SemanticsNode::new(node_id, role, actions, children, description)
2292}
2293
2294fn build_semantics_node_from_live_nodes(
2295    applier: &mut MemoryApplier,
2296    node: &MeasuredNode,
2297) -> Result<SemanticsNode, NodeError> {
2298    let (role, config) = match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
2299        let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
2300        let config = layout.semantics_configuration();
2301        layout.clear_needs_semantics();
2302        (role, config)
2303    }) {
2304        Ok(data) => data,
2305        Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
2306            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
2307                subcompose.clear_needs_semantics();
2308                (
2309                    SemanticsRole::Subcompose,
2310                    collect_semantics_from_modifier(&subcompose.modifier()),
2311                )
2312            }) {
2313                Ok(data) => data,
2314                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
2315                    (SemanticsRole::Unknown, None)
2316                }
2317                Err(err) => return Err(err),
2318            }
2319        }
2320        Err(err) => return Err(err),
2321    };
2322
2323    let mut children = Vec::with_capacity(node.children.len());
2324    for child in &node.children {
2325        children.push(build_semantics_node_from_live_nodes(applier, &child.node)?);
2326    }
2327
2328    Ok(semantics_node_from_parts(
2329        node.node_id,
2330        role,
2331        config,
2332        children,
2333    ))
2334}
2335
2336fn record_semantics_allocation_stats(node: &SemanticsNode, stats: &mut LayoutAllocationDebugStats) {
2337    stats.semantics_node_count += 1;
2338    stats.semantics_action_count += node.actions.len();
2339    stats.semantics_action_capacity += node.actions.capacity();
2340    stats.semantics_child_count += node.children.len();
2341    stats.semantics_child_capacity += node.children.capacity();
2342    stats.semantics_heap_bytes += node.actions.capacity() * size_of::<SemanticsAction>();
2343    stats.semantics_heap_bytes += node.children.capacity() * size_of::<SemanticsNode>();
2344
2345    if let Some(description) = &node.description {
2346        stats.semantics_description_count += 1;
2347        stats.semantics_description_bytes += description.capacity();
2348        stats.semantics_heap_bytes += description.capacity();
2349    }
2350    if let SemanticsRole::Text { value } = &node.role {
2351        stats.semantics_text_role_bytes += value.capacity();
2352        stats.semantics_heap_bytes += value.capacity();
2353    }
2354
2355    for child in &node.children {
2356        record_semantics_allocation_stats(child, stats);
2357    }
2358}
2359
2360fn record_layout_box_allocation_stats(
2361    layout_box: &LayoutBox,
2362    stats: &mut LayoutAllocationDebugStats,
2363) {
2364    stats.layout_box_count += 1;
2365    stats.layout_box_child_count += layout_box.children.len();
2366    stats.layout_box_child_capacity += layout_box.children.capacity();
2367    stats.layout_box_heap_bytes += layout_box.children.capacity() * size_of::<LayoutBox>();
2368    stats.add_modifier_slice(layout_box.node_data.modifier_slices().debug_stats());
2369
2370    for child in &layout_box.children {
2371        record_layout_box_allocation_stats(child, stats);
2372    }
2373}
2374
2375fn build_layout_tree(
2376    applier: &mut MemoryApplier,
2377    node: &MeasuredNode,
2378) -> Result<LayoutTree, NodeError> {
2379    fn place(
2380        applier: &mut MemoryApplier,
2381        node: &MeasuredNode,
2382        origin: Point,
2383    ) -> Result<LayoutBox, NodeError> {
2384        // Include the node's own offset (from OffsetNode) in its position
2385        let top_left = Point {
2386            x: origin.x + node.offset.x,
2387            y: origin.y + node.offset.y,
2388        };
2389        let rect = GeometryRect {
2390            x: top_left.x,
2391            y: top_left.y,
2392            width: node.size.width,
2393            height: node.size.height,
2394        };
2395        let info = runtime_metadata_for(applier, node.node_id)?;
2396        let kind = layout_kind_from_metadata(node.node_id, &info);
2397        let RuntimeNodeMetadata {
2398            modifier,
2399            resolved_modifiers,
2400            modifier_slices,
2401            ..
2402        } = info;
2403        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
2404        let mut children = Vec::with_capacity(node.children.len());
2405        for child in &node.children {
2406            let child_origin = Point {
2407                x: top_left.x + child.offset.x,
2408                y: top_left.y + child.offset.y,
2409            };
2410            children.push(place(applier, &child.node, child_origin)?);
2411        }
2412        Ok(LayoutBox::new(
2413            node.node_id,
2414            rect,
2415            node.content_offset,
2416            data,
2417            children,
2418        ))
2419    }
2420
2421    Ok(LayoutTree::new(place(
2422        applier,
2423        node,
2424        Point { x: 0.0, y: 0.0 },
2425    )?))
2426}
2427
2428fn semantics_role_from_layout_box(layout_box: &LayoutBox) -> SemanticsRole {
2429    match &layout_box.node_data.kind {
2430        LayoutNodeKind::Subcompose => SemanticsRole::Subcompose,
2431        LayoutNodeKind::Spacer => SemanticsRole::Spacer,
2432        LayoutNodeKind::Unknown => SemanticsRole::Unknown,
2433        LayoutNodeKind::Button { .. } => SemanticsRole::Button,
2434        LayoutNodeKind::Layout => layout_box
2435            .node_data
2436            .modifier_slices()
2437            .text_content()
2438            .map(|text| SemanticsRole::Text {
2439                value: text.to_string(),
2440            })
2441            .unwrap_or(SemanticsRole::Layout),
2442    }
2443}
2444
2445fn build_semantics_node_from_layout_box(layout_box: &LayoutBox) -> SemanticsNode {
2446    let children = layout_box
2447        .children
2448        .iter()
2449        .map(build_semantics_node_from_layout_box)
2450        .collect();
2451
2452    semantics_node_from_parts(
2453        layout_box.node_id,
2454        semantics_role_from_layout_box(layout_box),
2455        collect_semantics_from_modifier(&layout_box.node_data.modifier),
2456        children,
2457    )
2458}
2459
2460fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
2461    match &info.role {
2462        SemanticsRole::Layout => LayoutNodeKind::Layout,
2463        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
2464        SemanticsRole::Text { .. } => {
2465            // Text content is now handled via TextModifierNode in the modifier chain
2466            // and collected in modifier_slices.text_content(). LayoutNodeKind should
2467            // reflect the layout policy (EmptyMeasurePolicy), not the content type.
2468            LayoutNodeKind::Layout
2469        }
2470        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
2471        SemanticsRole::Button => {
2472            let handler = info
2473                .button_handler
2474                .as_ref()
2475                .cloned()
2476                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
2477            LayoutNodeKind::Button { on_click: handler }
2478        }
2479        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
2480    }
2481}
2482
2483fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
2484    let horizontal = padding.horizontal_sum();
2485    let vertical = padding.vertical_sum();
2486    let min_width = (constraints.min_width - horizontal).max(0.0);
2487    let mut max_width = constraints.max_width;
2488    if max_width.is_finite() {
2489        max_width = (max_width - horizontal).max(0.0);
2490    }
2491    let min_height = (constraints.min_height - vertical).max(0.0);
2492    let mut max_height = constraints.max_height;
2493    if max_height.is_finite() {
2494        max_height = (max_height - vertical).max(0.0);
2495    }
2496    normalize_constraints(Constraints {
2497        min_width,
2498        max_width,
2499        min_height,
2500        max_height,
2501    })
2502}
2503
2504#[cfg(test)]
2505pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
2506    match alignment {
2507        HorizontalAlignment::Start => 0.0,
2508        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
2509        HorizontalAlignment::End => (available - child).max(0.0),
2510    }
2511}
2512
2513#[cfg(test)]
2514pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
2515    match alignment {
2516        VerticalAlignment::Top => 0.0,
2517        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
2518        VerticalAlignment::Bottom => (available - child).max(0.0),
2519    }
2520}
2521
2522fn resolve_dimension(
2523    base: f32,
2524    explicit: DimensionConstraint,
2525    min_override: Option<f32>,
2526    max_override: Option<f32>,
2527    min_limit: f32,
2528    max_limit: f32,
2529) -> f32 {
2530    let mut min_bound = min_limit;
2531    if let Some(min_value) = min_override {
2532        min_bound = min_bound.max(min_value);
2533    }
2534
2535    let mut max_bound = if max_limit.is_finite() {
2536        max_limit
2537    } else {
2538        max_override.unwrap_or(max_limit)
2539    };
2540    if let Some(max_value) = max_override {
2541        if max_bound.is_finite() {
2542            max_bound = max_bound.min(max_value);
2543        } else {
2544            max_bound = max_value;
2545        }
2546    }
2547    if max_bound < min_bound {
2548        max_bound = min_bound;
2549    }
2550
2551    let mut size = match explicit {
2552        DimensionConstraint::Points(points) => points,
2553        DimensionConstraint::Fraction(fraction) => {
2554            if max_limit.is_finite() {
2555                max_limit * fraction.clamp(0.0, 1.0)
2556            } else {
2557                base
2558            }
2559        }
2560        DimensionConstraint::Unspecified => base,
2561        // Intrinsic sizing is resolved at a higher level where we have access to children.
2562        // At this point we just use the base size as a fallback.
2563        DimensionConstraint::Intrinsic(_) => base,
2564    };
2565
2566    size = clamp_dimension(size, min_bound, max_bound);
2567    size = clamp_dimension(size, min_limit, max_limit);
2568    size.max(0.0)
2569}
2570
2571fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
2572    let mut result = value.max(min);
2573    if max.is_finite() {
2574        result = result.min(max);
2575    }
2576    result
2577}
2578
2579fn normalize_constraints(mut constraints: Constraints) -> Constraints {
2580    if constraints.max_width < constraints.min_width {
2581        constraints.max_width = constraints.min_width;
2582    }
2583    if constraints.max_height < constraints.min_height {
2584        constraints.max_height = constraints.min_height;
2585    }
2586    constraints
2587}
2588
2589#[cfg(test)]
2590#[path = "tests/layout_tests.rs"]
2591mod tests;