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