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