Skip to main content

cranpose_ui/layout/
mod.rs

1pub mod core;
2pub mod policies;
3
4use std::{
5    cell::{Cell, RefCell},
6    fmt,
7    mem::size_of,
8    rc::Rc,
9    sync::OnceLock,
10};
11
12use cranpose_core::{
13    Applier, ApplierHost, Composer, ConcreteApplierHost, MemoryApplier, Node, NodeError, NodeId,
14    Phase, RuntimeHandle, SlotTable, SlotsHost, SnapshotStateObserver,
15};
16
17use self::core::Measurable;
18use self::core::Placeable;
19#[cfg(test)]
20use self::core::{HorizontalAlignment, VerticalAlignment};
21use crate::modifier::{
22    collect_semantics_from_modifier, DimensionConstraint, EdgeInsets, Modifier, ModifierNodeSlices,
23    ModifierNodeSlicesDebugStats, Point, Rect as GeometryRect, ResolvedModifiers, Size,
24};
25
26use crate::subcompose_layout::{CachedBatchMeasureInputs, SubcomposeLayoutNode};
27use crate::widgets::nodes::{IntrinsicKind, LayoutNode, LayoutNodeCacheHandles, LayoutState};
28use cranpose_foundation::{
29    text::TextRange, InvalidationKind, ModifierNodeContext, NodeCapabilities,
30    SemanticsConfiguration,
31};
32use cranpose_ui_layout::{Constraints, MeasurePolicy, Placement};
33use web_time::Instant;
34
35/// Runtime context for modifier nodes during measurement.
36///
37/// Unlike `BasicModifierNodeContext`, this context accumulates invalidations
38/// that can be processed after measurement to set dirty flags on the LayoutNode.
39#[derive(Default)]
40pub(crate) struct LayoutNodeContext {
41    invalidations: Vec<InvalidationKind>,
42    update_requested: bool,
43    active_capabilities: Vec<NodeCapabilities>,
44}
45
46impl LayoutNodeContext {
47    pub(crate) fn new() -> Self {
48        Self::default()
49    }
50
51    pub(crate) fn take_invalidations(&mut self) -> Vec<InvalidationKind> {
52        std::mem::take(&mut self.invalidations)
53    }
54}
55
56impl ModifierNodeContext for LayoutNodeContext {
57    fn invalidate(&mut self, kind: InvalidationKind) {
58        if !self.invalidations.contains(&kind) {
59            self.invalidations.push(kind);
60        }
61    }
62
63    fn request_update(&mut self) {
64        self.update_requested = true;
65    }
66
67    fn push_active_capabilities(&mut self, capabilities: NodeCapabilities) {
68        self.active_capabilities.push(capabilities);
69    }
70
71    fn pop_active_capabilities(&mut self) {
72        self.active_capabilities.pop();
73    }
74}
75
76/// Forces all layout caches to be invalidated on the next measure by incrementing the epoch.
77///
78/// # ⚠️ Internal Use Only - NOT Public API
79///
80/// **This function is hidden from public documentation and MUST NOT be called by external code.**
81///
82/// Only `cranpose-app-shell` may call this for rare global events:
83/// - Window/viewport resize
84/// - Global font scale or density changes
85/// - Debug toggles that affect all layout
86///
87/// **This is O(entire app size) - extremely expensive!**
88///
89/// # For Local Changes
90///
91/// **Do NOT use this for scroll, single-node mutations, or any local layout change.**
92/// Instead, use the scoped repass mechanism:
93/// ```text
94/// cranpose_ui::schedule_layout_repass(node_id);
95/// ```
96///
97/// The scoped path bubbles dirty flags without invalidating all caches, giving you O(subtree) instead of O(app).
98#[doc(hidden)]
99pub fn invalidate_all_layout_caches() {
100    crate::render_state::invalidate_layout_cache_epoch();
101}
102
103fn layout_measure_telemetry_threshold_ms() -> Option<f64> {
104    static THRESHOLD_MS: OnceLock<Option<f64>> = OnceLock::new();
105    *THRESHOLD_MS.get_or_init(|| {
106        std::env::var("CRANPOSE_LAYOUT_MEASURE_TELEMETRY_MS")
107            .ok()
108            .and_then(|value| value.parse::<f64>().ok())
109            .filter(|value| value.is_finite() && *value >= 0.0)
110            .or_else(|| {
111                std::env::var_os("CRANPOSE_LAYOUT_MEASURE_TELEMETRY")
112                    .is_some()
113                    .then_some(4.0)
114            })
115    })
116}
117
118struct LayoutMeasureTelemetry {
119    root: NodeId,
120    start: Instant,
121    after_repasses: Instant,
122    after_guard: Instant,
123    after_builder: Instant,
124    after_measure: Instant,
125    after_root_place: Instant,
126    after_aux: Instant,
127    after_builder_drop: Instant,
128    after_guard_drop: Instant,
129}
130
131fn log_layout_measure_telemetry(times: LayoutMeasureTelemetry) {
132    let Some(threshold_ms) = layout_measure_telemetry_threshold_ms() else {
133        return;
134    };
135
136    let total_ms = times
137        .after_guard_drop
138        .duration_since(times.start)
139        .as_secs_f64()
140        * 1000.0;
141    if total_ms < threshold_ms {
142        return;
143    }
144
145    let repass_ms = times
146        .after_repasses
147        .duration_since(times.start)
148        .as_secs_f64()
149        * 1000.0;
150    let guard_ms = times
151        .after_guard
152        .duration_since(times.after_repasses)
153        .as_secs_f64()
154        * 1000.0;
155    let builder_ms = times
156        .after_builder
157        .duration_since(times.after_guard)
158        .as_secs_f64()
159        * 1000.0;
160    let measure_ms = times
161        .after_measure
162        .duration_since(times.after_builder)
163        .as_secs_f64()
164        * 1000.0;
165    let root_place_ms = times
166        .after_root_place
167        .duration_since(times.after_measure)
168        .as_secs_f64()
169        * 1000.0;
170    let aux_ms = times
171        .after_aux
172        .duration_since(times.after_root_place)
173        .as_secs_f64()
174        * 1000.0;
175    let builder_drop_ms = times
176        .after_builder_drop
177        .duration_since(times.after_aux)
178        .as_secs_f64()
179        * 1000.0;
180    let guard_drop_ms = times
181        .after_guard_drop
182        .duration_since(times.after_builder_drop)
183        .as_secs_f64()
184        * 1000.0;
185    log::warn!(
186        "[layout-measure-telemetry] root={} total_ms={total_ms:.2} repass_ms={repass_ms:.2} guard_ms={guard_ms:.2} builder_ms={builder_ms:.2} measure_ms={measure_ms:.2} root_place_ms={root_place_ms:.2} aux_ms={aux_ms:.2} builder_drop_ms={builder_drop_ms:.2} guard_drop_ms={guard_drop_ms:.2}",
187        times.root
188    );
189}
190
191fn log_node_measure_telemetry(
192    kind: &'static str,
193    node_id: NodeId,
194    constraints: Constraints,
195    size: Size,
196    children: usize,
197    start: Instant,
198) {
199    let Some(threshold_ms) = layout_measure_telemetry_threshold_ms() else {
200        return;
201    };
202
203    let total_ms = start.elapsed().as_secs_f64() * 1000.0;
204    if total_ms < threshold_ms {
205        return;
206    }
207
208    log::warn!(
209        "[layout-node-telemetry] kind={kind} node={} total_ms={total_ms:.2} constraints=({:.1},{:.1},{:.1},{:.1}) size=({:.1},{:.1}) children={children}",
210        node_id,
211        constraints.min_width,
212        constraints.max_width,
213        constraints.min_height,
214        constraints.max_height,
215        size.width,
216        size.height,
217    );
218}
219
220/// RAII guard that:
221/// - moves the current MemoryApplier into a ConcreteApplierHost
222/// - holds a shared handle to the `SlotTable` used by `LayoutBuilder`
223/// - on Drop, always:
224///   * restores slots into the host from the shared handle
225///   * moves the original MemoryApplier back into the Composition
226///
227/// This makes `measure_layout` panic/Err-safe wrt both the applier and slots.
228/// The key invariant: guard and builder share the same `Rc<RefCell<SlotTable>>`,
229/// so the guard never loses access to the authoritative slots even on panic.
230struct ApplierSlotGuard<'a> {
231    /// The `MemoryApplier` inside the Composition::applier that we must restore into.
232    target: &'a mut MemoryApplier,
233    /// Host that owns the original MemoryApplier while layout is running.
234    host: Rc<ConcreteApplierHost<MemoryApplier>>,
235    /// Shared handle to the slot table. Both the guard and the builder hold a clone.
236    /// On Drop, we write whatever is in this handle back into the applier.
237    slots: Rc<RefCell<SlotTable>>,
238}
239
240impl<'a> ApplierSlotGuard<'a> {
241    /// Creates a new guard:
242    /// - moves the current MemoryApplier out of `target` into a host
243    /// - takes the current slots out of the host and wraps them in a shared handle
244    fn new(target: &'a mut MemoryApplier) -> Self {
245        // Move the original applier into a host; leave `target` with a fresh one
246        let original_applier = std::mem::replace(target, MemoryApplier::new());
247        let host = Rc::new(ConcreteApplierHost::new(original_applier));
248
249        // Take slots from the host into a shared handle
250        let slots = {
251            let mut applier_ref = host.borrow_typed();
252            std::mem::take(applier_ref.slots())
253        };
254        let slots = Rc::new(RefCell::new(slots));
255
256        Self {
257            target,
258            host,
259            slots,
260        }
261    }
262
263    /// Rc to pass into LayoutBuilder::new_with_epoch
264    fn host(&self) -> Rc<ConcreteApplierHost<MemoryApplier>> {
265        Rc::clone(&self.host)
266    }
267
268    /// Returns the shared handle to slots for the builder to use.
269    /// The builder clones this Rc, so both guard and builder share the same slots.
270    fn slots_handle(&self) -> Rc<RefCell<SlotTable>> {
271        Rc::clone(&self.slots)
272    }
273}
274
275impl Drop for ApplierSlotGuard<'_> {
276    fn drop(&mut self) {
277        // 1) Restore slots into the host's MemoryApplier from the shared handle.
278        // This works correctly whether we're on the success path or panic/error path,
279        // because we always have the shared handle.
280        {
281            let mut applier_ref = self.host.borrow_typed();
282            *applier_ref.slots() = std::mem::take(&mut *self.slots.borrow_mut());
283        }
284
285        // 2) Move the original MemoryApplier (with restored/updated slots) back into `target`
286        {
287            let mut applier_ref = self.host.borrow_typed();
288            let original_applier = std::mem::take(&mut *applier_ref);
289            let _ = std::mem::replace(self.target, original_applier);
290        }
291        // No Rc::try_unwrap in Drop → no "panic during panic" risk.
292    }
293}
294
295/// Result of measuring through the modifier node chain.
296struct ModifierChainMeasurement {
297    size: Size,
298    /// Content offset for scroll/inner transforms - NOT padding semantics
299    content_offset: Point,
300    /// Node's own offset (from OffsetNode, affects position in parent)
301    offset: Point,
302}
303
304type LayoutModifierNodeData = (
305    usize,
306    Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
307);
308
309struct ScratchVecPool<T> {
310    available: Vec<Vec<T>>,
311}
312
313impl<T> ScratchVecPool<T> {
314    fn acquire(&mut self) -> Vec<T> {
315        self.available.pop().unwrap_or_default()
316    }
317
318    fn release(&mut self, mut values: Vec<T>) {
319        values.clear();
320        self.available.push(values);
321    }
322
323    #[cfg(test)]
324    fn available_count(&self) -> usize {
325        self.available.len()
326    }
327}
328
329impl<T> Default for ScratchVecPool<T> {
330    fn default() -> Self {
331        Self {
332            available: Vec::new(),
333        }
334    }
335}
336
337#[derive(Default)]
338pub(crate) struct FrameLayoutArena {
339    tmp_records: ScratchVecPool<(NodeId, ChildRecord)>,
340    tmp_child_ids: ScratchVecPool<NodeId>,
341    tmp_layout_node_data: ScratchVecPool<LayoutModifierNodeData>,
342    tmp_placements: ScratchVecPool<Placement>,
343}
344
345#[cfg(test)]
346impl FrameLayoutArena {
347    pub(crate) fn available_placement_scratch_count(&self) -> usize {
348        self.tmp_placements.available_count()
349    }
350
351    pub(crate) fn seed_placement_scratch_for_test(&mut self) {
352        self.tmp_placements.release(Vec::with_capacity(1));
353    }
354}
355
356/// Discrete event callback reference produced during semantics extraction.
357#[derive(Clone, Debug, PartialEq, Eq)]
358pub struct SemanticsCallback {
359    node_id: NodeId,
360}
361
362impl SemanticsCallback {
363    pub fn new(node_id: NodeId) -> Self {
364        Self { node_id }
365    }
366
367    pub fn node_id(&self) -> NodeId {
368        self.node_id
369    }
370}
371
372/// Semantics action exposed to the input system.
373#[derive(Clone, Debug, PartialEq, Eq)]
374pub enum SemanticsAction {
375    Click { handler: SemanticsCallback },
376}
377
378/// Semantic role describing how a node should participate in accessibility and hit testing.
379/// Roles are now derived from SemanticsConfiguration rather than widget types.
380#[derive(Clone, Debug, PartialEq, Eq)]
381pub enum SemanticsRole {
382    /// Generic container or layout node
383    Layout,
384    /// Subcomposition boundary
385    Subcompose,
386    /// Text content derived from the text node semantics payload.
387    Text { value: String },
388    /// Spacer (non-interactive)
389    Spacer,
390    /// Button (derived from is_button semantics flag)
391    Button,
392    /// Unknown or unspecified role
393    Unknown,
394}
395
396/// A single node within the semantics tree.
397#[derive(Clone, Debug, PartialEq, Eq)]
398pub struct SemanticsNode {
399    pub node_id: NodeId,
400    pub role: SemanticsRole,
401    pub actions: Vec<SemanticsAction>,
402    pub children: Vec<SemanticsNode>,
403    pub description: Option<String>,
404    pub editable_text: bool,
405    pub text_selection: Option<TextRange>,
406}
407
408impl SemanticsNode {
409    fn new(
410        node_id: NodeId,
411        role: SemanticsRole,
412        actions: Vec<SemanticsAction>,
413        children: Vec<SemanticsNode>,
414        description: Option<String>,
415        editable_text: bool,
416        text_selection: Option<TextRange>,
417    ) -> Self {
418        Self {
419            node_id,
420            role,
421            actions,
422            children,
423            description,
424            editable_text,
425            text_selection,
426        }
427    }
428}
429
430/// Rooted semantics tree extracted after layout.
431#[derive(Clone, Debug, PartialEq, Eq)]
432pub struct SemanticsTree {
433    root: SemanticsNode,
434}
435
436impl SemanticsTree {
437    fn new(root: SemanticsNode) -> Self {
438        Self { root }
439    }
440
441    pub fn root(&self) -> &SemanticsNode {
442        &self.root
443    }
444}
445
446#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
447pub struct LayoutAllocationDebugStats {
448    pub layout_box_count: usize,
449    pub layout_box_child_count: usize,
450    pub layout_box_child_capacity: usize,
451    pub layout_box_heap_bytes: usize,
452    pub modifier_slice_count: usize,
453    pub modifier_slice_heap_bytes: usize,
454    pub modifier_draw_command_count: usize,
455    pub modifier_draw_command_capacity: usize,
456    pub modifier_pointer_input_count: usize,
457    pub modifier_pointer_input_capacity: usize,
458    pub modifier_click_handler_count: usize,
459    pub modifier_click_handler_capacity: usize,
460    pub modifier_text_content_count: usize,
461    pub modifier_text_style_count: usize,
462    pub modifier_text_layout_options_count: usize,
463    pub modifier_prepared_text_layout_count: usize,
464    pub modifier_graphics_layer_count: usize,
465    pub modifier_graphics_layer_resolver_count: usize,
466    pub semantics_node_count: usize,
467    pub semantics_action_count: usize,
468    pub semantics_action_capacity: usize,
469    pub semantics_child_count: usize,
470    pub semantics_child_capacity: usize,
471    pub semantics_description_count: usize,
472    pub semantics_description_bytes: usize,
473    pub semantics_text_role_bytes: usize,
474    pub semantics_heap_bytes: usize,
475}
476
477impl LayoutAllocationDebugStats {
478    fn add_modifier_slice(&mut self, stats: ModifierNodeSlicesDebugStats) {
479        self.modifier_slice_count += 1;
480        self.modifier_slice_heap_bytes += stats.heap_bytes;
481        self.modifier_draw_command_count += stats.draw_command_count;
482        self.modifier_draw_command_capacity += stats.draw_command_capacity;
483        self.modifier_pointer_input_count += stats.pointer_input_count;
484        self.modifier_pointer_input_capacity += stats.pointer_input_capacity;
485        self.modifier_click_handler_count += stats.click_handler_count;
486        self.modifier_click_handler_capacity += stats.click_handler_capacity;
487        self.modifier_text_content_count += usize::from(stats.has_text_content);
488        self.modifier_text_style_count += usize::from(stats.has_text_style);
489        self.modifier_text_layout_options_count += usize::from(stats.has_text_layout_options);
490        self.modifier_prepared_text_layout_count += usize::from(stats.has_prepared_text_layout);
491        self.modifier_graphics_layer_count += usize::from(stats.has_graphics_layer);
492        self.modifier_graphics_layer_resolver_count +=
493            usize::from(stats.has_graphics_layer_resolver);
494    }
495}
496
497/// Result of running layout for a Compose tree.
498#[derive(Debug, Clone)]
499pub struct LayoutTree {
500    root: LayoutBox,
501}
502
503impl LayoutTree {
504    pub fn new(root: LayoutBox) -> Self {
505        Self { root }
506    }
507
508    pub fn root(&self) -> &LayoutBox {
509        &self.root
510    }
511
512    pub fn root_mut(&mut self) -> &mut LayoutBox {
513        &mut self.root
514    }
515
516    pub fn into_root(self) -> LayoutBox {
517        self.root
518    }
519
520    pub fn debug_allocation_stats(&self) -> LayoutAllocationDebugStats {
521        let mut stats = LayoutAllocationDebugStats::default();
522        record_layout_box_allocation_stats(&self.root, &mut stats);
523        stats
524    }
525}
526
527/// Layout information for a single node.
528#[derive(Debug, Clone)]
529pub struct LayoutBox {
530    pub node_id: NodeId,
531    pub rect: GeometryRect,
532    /// Content offset for scroll/inner transforms (applies to children, NOT this node's position)
533    pub content_offset: Point,
534    pub node_data: LayoutNodeData,
535    pub children: Vec<LayoutBox>,
536}
537
538impl LayoutBox {
539    pub fn new(
540        node_id: NodeId,
541        rect: GeometryRect,
542        content_offset: Point,
543        node_data: LayoutNodeData,
544        children: Vec<LayoutBox>,
545    ) -> Self {
546        Self {
547            node_id,
548            rect,
549            content_offset,
550            node_data,
551            children,
552        }
553    }
554}
555
556/// Snapshot of the data required to render a layout node.
557#[derive(Debug, Clone)]
558pub struct LayoutNodeData {
559    pub modifier: Modifier,
560    pub resolved_modifiers: ResolvedModifiers,
561    pub modifier_slices: Rc<ModifierNodeSlices>,
562    pub kind: LayoutNodeKind,
563}
564
565impl LayoutNodeData {
566    pub fn new(
567        modifier: Modifier,
568        resolved_modifiers: ResolvedModifiers,
569        modifier_slices: Rc<ModifierNodeSlices>,
570        kind: LayoutNodeKind,
571    ) -> Self {
572        Self {
573            modifier,
574            resolved_modifiers,
575            modifier_slices,
576            kind,
577        }
578    }
579
580    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
581        self.resolved_modifiers
582    }
583
584    pub fn modifier_slices(&self) -> &ModifierNodeSlices {
585        &self.modifier_slices
586    }
587}
588
589/// Classification of the node captured inside a [`LayoutBox`].
590///
591/// Note: Text content is no longer represented as a distinct LayoutNodeKind.
592/// Text nodes now use `LayoutNodeKind::Layout` with their content stored in
593/// `modifier_slices.text_content()` via TextModifierNode, following Jetpack
594/// Compose's pattern where text is a modifier node capability.
595#[derive(Clone)]
596pub enum LayoutNodeKind {
597    Layout,
598    Subcompose,
599    Spacer,
600    Button { on_click: Rc<RefCell<dyn FnMut()>> },
601    Unknown,
602}
603
604impl fmt::Debug for LayoutNodeKind {
605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
606        match self {
607            LayoutNodeKind::Layout => f.write_str("Layout"),
608            LayoutNodeKind::Subcompose => f.write_str("Subcompose"),
609            LayoutNodeKind::Spacer => f.write_str("Spacer"),
610            LayoutNodeKind::Button { .. } => f.write_str("Button"),
611            LayoutNodeKind::Unknown => f.write_str("Unknown"),
612        }
613    }
614}
615
616/// Extension trait that equips `MemoryApplier` with layout computation.
617pub trait LayoutEngine {
618    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError>;
619}
620
621impl LayoutEngine for MemoryApplier {
622    fn compute_layout(&mut self, root: NodeId, max_size: Size) -> Result<LayoutTree, NodeError> {
623        let measurements = measure_layout(self, root, max_size)?;
624        measurements
625            .into_layout_tree()
626            .ok_or(NodeError::MissingContext {
627                id: root,
628                reason: "layout tree was not requested",
629            })
630    }
631}
632
633/// Result of running the measure pass for a Compose layout tree.
634#[derive(Debug, Clone)]
635pub struct LayoutMeasurements {
636    root: Rc<MeasuredNode>,
637    semantics: Option<SemanticsTree>,
638    layout_tree: Option<LayoutTree>,
639}
640
641impl LayoutMeasurements {
642    fn new(
643        root: Rc<MeasuredNode>,
644        semantics: Option<SemanticsTree>,
645        layout_tree: Option<LayoutTree>,
646    ) -> Self {
647        Self {
648            root,
649            semantics,
650            layout_tree,
651        }
652    }
653
654    /// Returns the measured size of the root node.
655    pub fn root_size(&self) -> Size {
656        self.root.size
657    }
658
659    pub fn semantics_tree(&self) -> Option<&SemanticsTree> {
660        self.semantics.as_ref()
661    }
662
663    pub fn debug_allocation_stats(&self) -> LayoutAllocationDebugStats {
664        let mut stats = self
665            .layout_tree
666            .as_ref()
667            .map(LayoutTree::debug_allocation_stats)
668            .unwrap_or_default();
669        if let Some(semantics) = &self.semantics {
670            record_semantics_allocation_stats(semantics.root(), &mut stats);
671        }
672        stats
673    }
674
675    /// Consumes the measurements and returns the built [`LayoutTree`], if requested.
676    pub fn into_layout_tree(self) -> Option<LayoutTree> {
677        self.layout_tree
678    }
679
680    /// Returns a cloned [`LayoutTree`] for rendering/debug consumers, if requested.
681    pub fn layout_tree(&self) -> Option<LayoutTree> {
682        self.layout_tree.clone()
683    }
684}
685
686/// Builds a semantics tree from an existing [`LayoutTree`].
687///
688/// This is useful for consumers that need semantics on demand without forcing
689/// every layout pass to eagerly allocate a full [`SemanticsTree`].
690pub fn build_semantics_tree_from_layout_tree(layout_tree: &LayoutTree) -> SemanticsTree {
691    SemanticsTree::new(build_semantics_node_from_layout_box(layout_tree.root()))
692}
693
694/// Builds a layout snapshot from retained layout state in the live applier tree.
695///
696/// Renderers use retained node state directly. This function exists for debug,
697/// robot, and tests that need an owned [`LayoutTree`] without forcing every
698/// layout pass to allocate one.
699pub fn build_layout_tree_from_applier(
700    applier: &mut MemoryApplier,
701    root: NodeId,
702) -> Result<Option<LayoutTree>, NodeError> {
703    fn snapshot(
704        applier: &mut MemoryApplier,
705        node_id: NodeId,
706    ) -> Result<Option<(crate::widgets::nodes::layout_node::LayoutState, Vec<NodeId>)>, NodeError>
707    {
708        match applier.with_node::<LayoutNode, _>(node_id, |node| {
709            (node.layout_state(), node.children.clone())
710        }) {
711            Ok(snapshot) => return Ok(Some(snapshot)),
712            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
713            Err(err) => return Err(err),
714        }
715
716        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
717            (node.layout_state(), node.active_children())
718        }) {
719            Ok(snapshot) => Ok(Some(snapshot)),
720            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
721            Err(err) => Err(err),
722        }
723    }
724
725    fn place(
726        applier: &mut MemoryApplier,
727        node_id: NodeId,
728        parent_content_origin: Point,
729    ) -> Result<Option<LayoutBox>, NodeError> {
730        let Some((state, child_ids)) = snapshot(applier, node_id)? else {
731            return Ok(None);
732        };
733        if !state.is_placed {
734            return Ok(None);
735        }
736
737        let top_left = Point {
738            x: parent_content_origin.x + state.position.x,
739            y: parent_content_origin.y + state.position.y,
740        };
741        let rect = GeometryRect {
742            x: top_left.x,
743            y: top_left.y,
744            width: state.size.width,
745            height: state.size.height,
746        };
747        let info = runtime_metadata_for(applier, node_id)?;
748        let kind = layout_kind_from_metadata(node_id, &info);
749        let RuntimeNodeMetadata {
750            modifier,
751            resolved_modifiers,
752            modifier_slices,
753            ..
754        } = info;
755        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
756        let child_origin = Point {
757            x: top_left.x + state.content_offset.x,
758            y: top_left.y + state.content_offset.y,
759        };
760        let mut children = Vec::with_capacity(child_ids.len());
761        for child_id in child_ids {
762            if let Some(child) = place(applier, child_id, child_origin)? {
763                children.push(child);
764            }
765        }
766
767        Ok(Some(LayoutBox::new(
768            node_id,
769            rect,
770            state.content_offset,
771            data,
772            children,
773        )))
774    }
775
776    place(applier, root, Point::default()).map(|root| root.map(LayoutTree::new))
777}
778
779/// Builds a semantics snapshot from retained layout state in the live applier tree.
780///
781/// This is the on-demand counterpart to [`build_layout_tree_from_applier`].
782/// It follows the currently placed child set, including subcompose active
783/// children, and clears semantics dirty flags for nodes it visits.
784pub fn build_semantics_tree_from_applier(
785    applier: &mut MemoryApplier,
786    root: NodeId,
787) -> Result<Option<SemanticsTree>, NodeError> {
788    fn node(
789        applier: &mut MemoryApplier,
790        node_id: NodeId,
791    ) -> Result<Option<SemanticsNode>, NodeError> {
792        match applier.with_node::<LayoutNode, _>(node_id, |layout| {
793            let state = layout.layout_state();
794            if !state.is_placed {
795                return None;
796            }
797            let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
798            let config = layout.semantics_configuration();
799            let children = layout.children.clone();
800            layout.clear_needs_semantics();
801            Some((role, config, children))
802        }) {
803            Ok(Some((role, config, child_ids))) => {
804                let mut children = Vec::with_capacity(child_ids.len());
805                for child_id in child_ids {
806                    if let Some(child) = node(applier, child_id)? {
807                        children.push(child);
808                    }
809                }
810                return Ok(Some(semantics_node_from_parts(
811                    node_id, role, config, children,
812                )));
813            }
814            Ok(None) => return Ok(None),
815            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
816            Err(err) => return Err(err),
817        }
818
819        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |subcompose| {
820            let state = subcompose.layout_state();
821            if !state.is_placed {
822                return None;
823            }
824            let config = collect_semantics_from_modifier(&subcompose.modifier());
825            let children = subcompose.active_children();
826            subcompose.clear_needs_semantics();
827            Some((config, children))
828        }) {
829            Ok(Some((config, child_ids))) => {
830                let mut children = Vec::with_capacity(child_ids.len());
831                for child_id in child_ids {
832                    if let Some(child) = node(applier, child_id)? {
833                        children.push(child);
834                    }
835                }
836                Ok(Some(semantics_node_from_parts(
837                    node_id,
838                    SemanticsRole::Subcompose,
839                    config,
840                    children,
841                )))
842            }
843            Ok(None) | Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
844                Ok(None)
845            }
846            Err(err) => Err(err),
847        }
848    }
849
850    node(applier, root).map(|root| root.map(SemanticsTree::new))
851}
852
853#[derive(Clone, Copy, Debug, PartialEq, Eq)]
854pub struct MeasureLayoutOptions {
855    pub collect_semantics: bool,
856    pub build_layout_tree: bool,
857}
858
859impl Default for MeasureLayoutOptions {
860    fn default() -> Self {
861        Self {
862            collect_semantics: true,
863            build_layout_tree: true,
864        }
865    }
866}
867
868/// Check if a node or any of its descendants needs measure (selective measure optimization).
869/// This can be used by the app shell to skip layout when the tree is clean.
870///
871/// O(1) check - just looks at root's dirty flag.
872/// Works because all mutation paths bubble dirty flags to root via composer commands.
873///
874/// Returns Result to force caller to handle errors explicitly. No more unwrap_or(true) safety net.
875pub fn tree_needs_layout(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
876    Ok(applier.get_mut(root)?.needs_layout())
877}
878
879/// Check if the root semantics snapshot is dirty.
880///
881/// Semantics invalidations bubble to the root the same way layout invalidations do,
882/// so a root check is sufficient to determine whether the next layout pass needs to
883/// rebuild semantic data even when geometry is otherwise unchanged.
884pub fn tree_needs_semantics(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
885    Ok(applier.get_mut(root)?.needs_semantics())
886}
887
888/// Test helper: bubbles layout dirty flag to root.
889#[cfg(test)]
890pub(crate) fn bubble_layout_dirty(applier: &mut MemoryApplier, node_id: NodeId) {
891    cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
892}
893
894/// Runs the measure phase for the subtree rooted at `root`.
895pub fn measure_layout(
896    applier: &mut MemoryApplier,
897    root: NodeId,
898    max_size: Size,
899) -> Result<LayoutMeasurements, NodeError> {
900    measure_layout_with_options(applier, root, max_size, MeasureLayoutOptions::default())
901}
902
903pub fn measure_layout_with_options(
904    applier: &mut MemoryApplier,
905    root: NodeId,
906    max_size: Size,
907    options: MeasureLayoutOptions,
908) -> Result<LayoutMeasurements, NodeError> {
909    let telemetry_start = Instant::now();
910    process_pending_layout_repasses(applier, root)?;
911    let after_repasses = Instant::now();
912
913    let constraints = Constraints {
914        min_width: 0.0,
915        max_width: max_size.width,
916        min_height: 0.0,
917        max_height: max_size.height,
918    };
919
920    // Selective measure: only increment epoch if something needs MEASURING (not just layout)
921    // O(1) check - just look at root's dirty flag (bubbling ensures correctness)
922    //
923    // CRITICAL: We check needs_MEASURE, not needs_LAYOUT!
924    // - needs_measure: size may change, caches must be invalidated
925    // - needs_layout: position may change but size is cached (e.g., scroll)
926    //
927    // Scroll operations bubble needs_layout to ancestors, but NOT needs_measure.
928    // Using needs_layout here would wipe ALL caches on every scroll frame, causing
929    // O(N) full remeasurement instead of O(changed nodes).
930    let (needs_remeasure, _needs_semantics, cached_epoch) = match applier
931        .with_node::<LayoutNode, _>(root, |node| {
932            (
933                node.needs_measure(), // CORRECT: check needs_measure, not needs_layout
934                node.needs_semantics(),
935                node.cache_handles().epoch(),
936            )
937        }) {
938        Ok(tuple) => tuple,
939        Err(NodeError::TypeMismatch { .. }) => {
940            let node = applier.get_mut(root)?;
941            // Non-LayoutNode roots still expose Node dirty flags.
942            // Use needs_measure here so layout-only subtree repasses can reuse
943            // the existing cache epoch instead of invalidating the whole tree.
944            let measure_dirty = node.needs_measure();
945            let semantics_dirty = node.needs_semantics();
946            (measure_dirty, semantics_dirty, 0)
947        }
948        Err(err) => return Err(err),
949    };
950
951    let epoch = if needs_remeasure {
952        crate::render_state::next_layout_cache_epoch()
953    } else if cached_epoch != 0 {
954        cached_epoch
955    } else {
956        // Fallback when caller root isn't a LayoutNode (e.g. tests using Spacer directly).
957        crate::render_state::current_layout_cache_epoch()
958    };
959
960    // Move the current applier into a host and set up a guard that will
961    // ALWAYS restore:
962    // - the MemoryApplier back into `applier`
963    // - the SlotTable back into that MemoryApplier
964    //
965    // IMPORTANT: Declare the guard *before* the builder so the builder
966    // is dropped first (both on Ok and on unwind).
967    let guard = ApplierSlotGuard::new(applier);
968    let applier_host = guard.host();
969    let slots_handle = guard.slots_handle();
970    let after_guard = Instant::now();
971
972    // Give the builder the shared slots handle - both guard and builder
973    // now share access to the same SlotTable via Rc<RefCell<_>>.
974    let frame_arena = crate::render_state::take_layout_frame_arena();
975    let mut builder = LayoutBuilder::new_with_epoch(
976        Rc::clone(&applier_host),
977        epoch,
978        Rc::clone(&slots_handle),
979        frame_arena,
980    );
981    let after_builder = Instant::now();
982
983    // ---- Measurement -------------------------------------------------------
984    // If measurement fails, the guard will restore slots from the shared handle
985    // on drop - this is safe because the handle always contains valid slots.
986
987    let measured = builder.measure_node(root, normalize_constraints(constraints))?;
988    let after_measure = Instant::now();
989
990    // Root node has no parent to place it, so we must explicitly place it at (0,0).
991    // This ensures is_placed=true, allowing the renderer to traverse the tree.
992    // Handle both LayoutNode and SubcomposeLayoutNode as potential roots.
993    if let Ok(mut applier) = applier_host.try_borrow_typed() {
994        if applier
995            .with_node::<LayoutNode, _>(root, |node| {
996                node.set_position(Point::default());
997            })
998            .is_err()
999        {
1000            let _ = applier.with_node::<SubcomposeLayoutNode, _>(root, |node| {
1001                node.set_position(Point::default());
1002            });
1003        }
1004    }
1005    let after_root_place = Instant::now();
1006
1007    let (layout_tree, semantics) = {
1008        let mut applier_ref = applier_host.borrow_typed();
1009        let layout_tree = if options.build_layout_tree {
1010            Some(build_layout_tree(&mut applier_ref, &measured)?)
1011        } else {
1012            None
1013        };
1014        let semantics = if options.collect_semantics {
1015            let semantics_tree = if let Some(layout_tree) = layout_tree.as_ref() {
1016                clear_semantics_dirty_flags(&mut applier_ref, &measured)?;
1017                build_semantics_tree_from_layout_tree(layout_tree)
1018            } else {
1019                build_semantics_tree_from_live_nodes(&mut applier_ref, &measured)?
1020            };
1021            Some(semantics_tree)
1022        } else {
1023            None
1024        };
1025        (layout_tree, semantics)
1026    };
1027    let after_aux = Instant::now();
1028
1029    // Drop builder before guard - slots are already in the shared handle.
1030    // Guard's Drop will write them back to the applier.
1031    drop(builder);
1032    let after_builder_drop = Instant::now();
1033
1034    // DO NOT manually unwrap `applier_host` or replace `applier` here.
1035    // `ApplierSlotGuard::drop` will restore everything when this function returns.
1036    drop(guard);
1037    let after_guard_drop = Instant::now();
1038
1039    log_layout_measure_telemetry(LayoutMeasureTelemetry {
1040        root,
1041        start: telemetry_start,
1042        after_repasses,
1043        after_guard,
1044        after_builder,
1045        after_measure,
1046        after_root_place,
1047        after_aux,
1048        after_builder_drop,
1049        after_guard_drop,
1050    });
1051
1052    Ok(LayoutMeasurements::new(measured, semantics, layout_tree))
1053}
1054
1055fn process_pending_layout_repasses(
1056    applier: &mut MemoryApplier,
1057    root: NodeId,
1058) -> Result<(), NodeError> {
1059    for node_id in crate::render_state::take_modifier_slice_repass_nodes() {
1060        if let Ok(node) = applier.get_mut(node_id) {
1061            let any = node.as_any_mut();
1062            if let Some(layout) = any.downcast_mut::<crate::widgets::nodes::LayoutNode>() {
1063                layout.mark_modifier_slices_dirty();
1064            } else if let Some(subcompose) =
1065                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1066            {
1067                subcompose.mark_modifier_slices_dirty();
1068            }
1069        }
1070    }
1071    // Measure repasses re-*size* a subtree (and its ancestors), so an enclosing
1072    // LazyColumn re-measures the node instead of reusing its cached item slot.
1073    let measure_repass_nodes = crate::take_measure_repass_nodes();
1074    let repass_nodes = crate::take_layout_repass_nodes();
1075    if measure_repass_nodes.is_empty() && repass_nodes.is_empty() {
1076        return Ok(());
1077    }
1078    for node_id in measure_repass_nodes {
1079        cranpose_core::bubble_measure_dirty(applier as &mut dyn Applier, node_id);
1080    }
1081    for node_id in repass_nodes {
1082        cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
1083    }
1084    applier.get_mut(root)?.mark_needs_layout();
1085    Ok(())
1086}
1087
1088struct LayoutBuilder {
1089    state: Rc<RefCell<LayoutBuilderState>>,
1090}
1091
1092impl LayoutBuilder {
1093    fn new_with_epoch(
1094        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1095        epoch: u64,
1096        slots: Rc<RefCell<SlotTable>>,
1097        frame_arena: FrameLayoutArena,
1098    ) -> Self {
1099        Self {
1100            state: Rc::new(RefCell::new(LayoutBuilderState::new_with_epoch(
1101                applier,
1102                epoch,
1103                slots,
1104                frame_arena,
1105            ))),
1106        }
1107    }
1108
1109    fn measure_node(
1110        &mut self,
1111        node_id: NodeId,
1112        constraints: Constraints,
1113    ) -> Result<Rc<MeasuredNode>, NodeError> {
1114        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
1115    }
1116
1117    fn set_runtime_handle(&mut self, handle: Option<RuntimeHandle>) {
1118        self.state.borrow_mut().runtime_handle = handle;
1119    }
1120}
1121
1122impl Drop for LayoutBuilder {
1123    fn drop(&mut self) {
1124        if Rc::strong_count(&self.state) != 1 {
1125            return;
1126        }
1127        let Ok(mut state) = self.state.try_borrow_mut() else {
1128            return;
1129        };
1130        crate::render_state::replace_layout_frame_arena(std::mem::take(&mut state.frame_arena));
1131    }
1132}
1133
1134struct LayoutBuilderState {
1135    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1136    runtime_handle: Option<RuntimeHandle>,
1137    /// Shared handle to the slot table. This is shared with ApplierSlotGuard
1138    /// to ensure panic-safety: even if we panic, the guard can restore slots.
1139    slots: Rc<RefCell<SlotTable>>,
1140    cache_epoch: u64,
1141    frame_arena: FrameLayoutArena,
1142}
1143
1144struct LayoutRuntimeFrameBindingCleanup {
1145    state: Rc<RefCell<LayoutRuntimeState>>,
1146}
1147
1148impl LayoutRuntimeFrameBindingCleanup {
1149    fn new(state: Rc<RefCell<LayoutRuntimeState>>) -> Self {
1150        Self { state }
1151    }
1152}
1153
1154impl Drop for LayoutRuntimeFrameBindingCleanup {
1155    fn drop(&mut self) {
1156        self.state.borrow().clear_frame_bindings();
1157    }
1158}
1159
1160impl LayoutBuilderState {
1161    fn new_with_epoch(
1162        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1163        epoch: u64,
1164        slots: Rc<RefCell<SlotTable>>,
1165        frame_arena: FrameLayoutArena,
1166    ) -> Self {
1167        let runtime_handle = applier.borrow_typed().runtime_handle();
1168
1169        Self {
1170            applier,
1171            runtime_handle,
1172            slots,
1173            cache_epoch: epoch,
1174            frame_arena,
1175        }
1176    }
1177
1178    fn try_with_applier_result<R>(
1179        state_rc: &Rc<RefCell<Self>>,
1180        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
1181    ) -> Option<Result<R, NodeError>> {
1182        let host = {
1183            let state = state_rc.borrow();
1184            Rc::clone(&state.applier)
1185        };
1186
1187        // Try to borrow - if already borrowed (nested call), return None
1188        let Ok(mut applier) = host.try_borrow_typed() else {
1189            return None;
1190        };
1191
1192        Some(f(&mut applier))
1193    }
1194
1195    fn with_applier_result<R>(
1196        state_rc: &Rc<RefCell<Self>>,
1197        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
1198    ) -> Result<R, NodeError> {
1199        Self::try_with_applier_result(state_rc, f).unwrap_or_else(|| {
1200            Err(NodeError::MissingContext {
1201                id: NodeId::default(),
1202                reason: "applier already borrowed",
1203            })
1204        })
1205    }
1206
1207    /// Clears the is_placed flag for a node at the start of measurement.
1208    /// This ensures nodes that drop out of placement won't render with stale geometry.
1209    fn clear_node_placed(state_rc: &Rc<RefCell<Self>>, node_id: NodeId) {
1210        let host = {
1211            let state = state_rc.borrow();
1212            Rc::clone(&state.applier)
1213        };
1214        let Ok(mut applier) = host.try_borrow_typed() else {
1215            return;
1216        };
1217        // Try LayoutNode first, then SubcomposeLayoutNode
1218        if applier
1219            .with_node::<LayoutNode, _>(node_id, |node| {
1220                node.clear_placed();
1221            })
1222            .is_err()
1223        {
1224            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1225                node.clear_placed();
1226            });
1227        }
1228    }
1229
1230    fn measure_node(
1231        state_rc: Rc<RefCell<Self>>,
1232        node_id: NodeId,
1233        constraints: Constraints,
1234    ) -> Result<Rc<MeasuredNode>, NodeError> {
1235        let telemetry_start = Instant::now();
1236        // Clear is_placed at the start of measurement.
1237        // Nodes that are placed will have is_placed set to true via Placeable::place().
1238        // Nodes that drop out of placement (not placed this pass) will remain is_placed=false.
1239        Self::clear_node_placed(&state_rc, node_id);
1240
1241        // Try SubcomposeLayoutNode first
1242        if let Some(subcompose) =
1243            Self::try_measure_subcompose(Rc::clone(&state_rc), node_id, constraints)?
1244        {
1245            log_node_measure_telemetry(
1246                "subcompose",
1247                node_id,
1248                constraints,
1249                subcompose.size,
1250                subcompose.children.len(),
1251                telemetry_start,
1252            );
1253            return Ok(subcompose);
1254        }
1255
1256        // Try LayoutNode (the primary modern path)
1257        if let Some(result) = Self::try_with_applier_result(&state_rc, |applier| {
1258            match applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1259                LayoutNodeSnapshot::from_layout_node(layout_node)
1260            }) {
1261                Ok(snapshot) => Ok(Some(snapshot)),
1262                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
1263                Err(err) => Err(err),
1264            }
1265        }) {
1266            // Applier was available, process the result
1267            if let Some(snapshot) = result? {
1268                let measured = Self::measure_layout_node(
1269                    Rc::clone(&state_rc),
1270                    node_id,
1271                    snapshot,
1272                    constraints,
1273                )?;
1274                log_node_measure_telemetry(
1275                    "layout",
1276                    node_id,
1277                    constraints,
1278                    measured.size,
1279                    measured.children.len(),
1280                    telemetry_start,
1281                );
1282                return Ok(measured);
1283            }
1284        }
1285        // If applier was busy (None) or snapshot was None, fall through to fallback
1286
1287        // No alternate fallbacks - all widgets use LayoutNode or SubcomposeLayoutNode
1288        // If we reach here, it's an unknown node type (shouldn't happen in normal use)
1289        let measured = Rc::new(MeasuredNode::new(
1290            node_id,
1291            Size::default(),
1292            Point { x: 0.0, y: 0.0 },
1293            Point::default(), // No content offset for fallback nodes
1294            Vec::new(),
1295        ));
1296        log_node_measure_telemetry(
1297            "fallback",
1298            node_id,
1299            constraints,
1300            measured.size,
1301            measured.children.len(),
1302            telemetry_start,
1303        );
1304        Ok(measured)
1305    }
1306
1307    fn cached_measure_node_with_applier(
1308        applier: &mut MemoryApplier,
1309        node_id: NodeId,
1310        constraints: Constraints,
1311    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1312        let Some(data) = Self::layout_child_measure_data(applier, node_id)? else {
1313            return Ok(None);
1314        };
1315        if data.needs_measure || data.cache.epoch() == 0 {
1316            return Ok(None);
1317        }
1318
1319        let Some(measured) = data.cache.get_measurement(constraints) else {
1320            return Ok(None);
1321        };
1322
1323        if let Some(layout_state) = data.layout_state {
1324            let mut layout_state = layout_state.borrow_mut();
1325            layout_state.size = measured.size;
1326            layout_state.measurement_constraints = constraints;
1327            drop(layout_state);
1328            let _ = applier.with_node::<LayoutNode, _>(node_id, |node| {
1329                if data.needs_layout {
1330                    node.clear_needs_layout();
1331                }
1332            });
1333        } else {
1334            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1335                node.set_measured_size(measured.size);
1336                if data.needs_layout {
1337                    node.clear_needs_layout();
1338                }
1339            });
1340        }
1341
1342        Ok(Some(measured))
1343    }
1344
1345    fn try_measure_subcompose(
1346        state_rc: Rc<RefCell<Self>>,
1347        node_id: NodeId,
1348        constraints: Constraints,
1349    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1350        let applier_host = {
1351            let state = state_rc.borrow();
1352            Rc::clone(&state.applier)
1353        };
1354
1355        let (node_handle, resolved_modifiers) = {
1356            // Try to borrow - if already borrowed (nested measurement), return None
1357            let Ok(mut applier) = applier_host.try_borrow_typed() else {
1358                return Ok(None);
1359            };
1360            let node = match applier.get_mut(node_id) {
1361                Ok(node) => node,
1362                Err(NodeError::Missing { .. }) => return Ok(None),
1363                Err(err) => return Err(err),
1364            };
1365            let any = node.as_any_mut();
1366            if let Some(subcompose) =
1367                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1368            {
1369                let handle = subcompose.handle();
1370                let resolved_modifiers = handle.resolved_modifiers();
1371                (handle, resolved_modifiers)
1372            } else {
1373                return Ok(None);
1374            }
1375        };
1376
1377        let runtime_handle = {
1378            let mut state = state_rc.borrow_mut();
1379            if state.runtime_handle.is_none() {
1380                // Try to borrow - if already borrowed, we can't get runtime handle
1381                if let Ok(applier) = applier_host.try_borrow_typed() {
1382                    state.runtime_handle = applier.runtime_handle();
1383                }
1384            }
1385            state
1386                .runtime_handle
1387                .clone()
1388                .ok_or(NodeError::MissingContext {
1389                    id: node_id,
1390                    reason: "runtime handle required for subcomposition",
1391                })?
1392        };
1393
1394        let props = resolved_modifiers.layout_properties();
1395        let padding = resolved_modifiers.padding();
1396        let offset = resolved_modifiers.offset();
1397        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
1398
1399        if let DimensionConstraint::Points(width) = props.width() {
1400            let constrained_width = width - padding.horizontal_sum();
1401            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
1402            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
1403        }
1404        if let DimensionConstraint::Points(height) = props.height() {
1405            let constrained_height = height - padding.vertical_sum();
1406            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
1407            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
1408        }
1409
1410        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
1411        let slots_host = slots_guard.host();
1412        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
1413        let observer = SnapshotStateObserver::new(|callback| callback());
1414        let composer = Composer::new(
1415            Rc::clone(&slots_host),
1416            applier_host_dyn,
1417            runtime_handle.clone(),
1418            observer,
1419            Some(node_id),
1420        );
1421        composer.enter_phase(Phase::Measure);
1422
1423        let state_rc_clone = Rc::clone(&state_rc);
1424        let measure_error = RefCell::new(None);
1425        let state_rc_for_subcompose = Rc::clone(&state_rc_clone);
1426        let error_for_subcompose = &measure_error;
1427        let measured_children = node_handle.measured_children_scratch();
1428        let measured_children_for_subcompose = Rc::clone(&measured_children);
1429        let state_rc_for_cached = Rc::clone(&state_rc_clone);
1430        let error_for_cached = &measure_error;
1431        let measured_children_for_cached = Rc::clone(&measured_children);
1432        let measured_children_for_lookup = Rc::clone(&measured_children);
1433        let measured_children_for_retained = Rc::clone(&measured_children);
1434
1435        let measure_result = node_handle.measure_with_cached_batch(
1436            &composer,
1437            node_id,
1438            inner_constraints,
1439            CachedBatchMeasureInputs {
1440                measurer: Box::new(
1441                    move |child_id: NodeId, child_constraints: Constraints| -> Size {
1442                        match Self::measure_node(
1443                            Rc::clone(&state_rc_for_subcompose),
1444                            child_id,
1445                            child_constraints,
1446                        ) {
1447                            Ok(measured) => {
1448                                measured_children_for_subcompose
1449                                    .borrow_mut()
1450                                    .insert(child_id, Rc::clone(&measured));
1451                                measured.size
1452                            }
1453                            Err(err) => {
1454                                let mut slot = error_for_subcompose.borrow_mut();
1455                                if slot.is_none() {
1456                                    *slot = Some(err);
1457                                }
1458                                Size::default()
1459                            }
1460                        }
1461                    },
1462                ),
1463                cached_measure_batch_registrar: Box::new(
1464                    move |child_ids: &[NodeId],
1465                          child_constraints: Constraints,
1466                          out: &mut Vec<Option<Size>>| {
1467                        out.clear();
1468                        out.resize(child_ids.len(), None);
1469
1470                        let applier_host = {
1471                            let state = state_rc_for_cached.borrow();
1472                            Rc::clone(&state.applier)
1473                        };
1474                        let Ok(mut applier) = applier_host.try_borrow_typed() else {
1475                            return;
1476                        };
1477
1478                        let mut measured_children = measured_children_for_cached.borrow_mut();
1479                        for (index, &child_id) in child_ids.iter().enumerate() {
1480                            match Self::cached_measure_node_with_applier(
1481                                &mut applier,
1482                                child_id,
1483                                child_constraints,
1484                            ) {
1485                                Ok(Some(measured)) => {
1486                                    out[index] = Some(measured.size);
1487                                    measured_children.insert(child_id, Rc::clone(&measured));
1488                                }
1489                                Ok(None) => {}
1490                                Err(err) => {
1491                                    let mut slot = error_for_cached.borrow_mut();
1492                                    if slot.is_none() {
1493                                        *slot = Some(err);
1494                                    }
1495                                    break;
1496                                }
1497                            }
1498                        }
1499                    },
1500                ),
1501                retained_measure_lookup: Box::new(move |child_id| {
1502                    measured_children_for_lookup
1503                        .borrow()
1504                        .get(&child_id)
1505                        .cloned()
1506                }),
1507                retained_measure_registrar: Box::new(move |measurements| {
1508                    let mut measured_children = measured_children_for_retained.borrow_mut();
1509                    for measured in measurements {
1510                        measured_children.insert(measured.node_id(), Rc::clone(measured));
1511                    }
1512                }),
1513                error: &measure_error,
1514            },
1515        )?;
1516        drop(composer);
1517        slots_guard.restore(slots_host.into_table()?);
1518
1519        if let Some(err) = measure_error.borrow_mut().take() {
1520            return Err(err);
1521        }
1522
1523        // NOTE: Children are now managed by the composer via insert_child commands
1524        // (from parent_stack initialization with root). set_active_children is no longer used.
1525
1526        let cranpose_ui_layout::MeasureResult {
1527            size: measured_size,
1528            placements,
1529        } = measure_result;
1530
1531        let mut width = measured_size.width + padding.horizontal_sum();
1532        let mut height = measured_size.height + padding.vertical_sum();
1533
1534        width = resolve_dimension(
1535            width,
1536            props.width(),
1537            props.min_width(),
1538            props.max_width(),
1539            constraints.min_width,
1540            constraints.max_width,
1541        );
1542        height = resolve_dimension(
1543            height,
1544            props.height(),
1545            props.min_height(),
1546            props.max_height(),
1547            constraints.min_height,
1548            constraints.max_height,
1549        );
1550
1551        let mut children = Vec::with_capacity(placements.len());
1552        let mut measured_children_by_id = measured_children.borrow_mut();
1553
1554        // Update the SubcomposeLayoutNode's size (position will be set by parent's placement)
1555        if let Ok(mut applier) = applier_host.try_borrow_typed() {
1556            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
1557                parent_node.set_measured_size(Size { width, height });
1558                parent_node.clear_needs_measure();
1559                parent_node.clear_needs_layout();
1560            });
1561        }
1562
1563        for placement in &placements {
1564            let child = if let Some(measured) = measured_children_by_id.remove(&placement.node_id) {
1565                measured
1566            } else {
1567                // Policies may place subcomposed children without calling `measure()` first
1568                // (for example, when they only need a slot's rendered content). Keep the
1569                // existing fallback for that case, but preserve the policy-time measurement
1570                // whenever it exists so we don't silently remeasure lazy items with the
1571                // container's tighter constraints.
1572                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?
1573            };
1574            let position = Point {
1575                x: padding.left + placement.x,
1576                y: padding.top + placement.y,
1577            };
1578
1579            // Critical: Update the child's retained placement state.
1580            // Standard layouts do this via Placeable::place(), but SubcomposeLayout
1581            // logic bypasses Placeables and returns raw Placements. A subcomposed
1582            // child can itself be a SubcomposeLayout (e.g. a `BoxWithConstraints`
1583            // inside a `LazyColumn` item), so both node kinds must be positioned
1584            // and marked placed; otherwise the applier-traversal render, layout,
1585            // and semantics builds cull the child's whole subtree (issue #305).
1586            if let Ok(mut applier) = applier_host.try_borrow_typed() {
1587                if applier
1588                    .with_node::<LayoutNode, _>(placement.node_id, |node| {
1589                        node.set_position(position);
1590                    })
1591                    .is_err()
1592                {
1593                    let _ =
1594                        applier.with_node::<SubcomposeLayoutNode, _>(placement.node_id, |node| {
1595                            node.set_position(position);
1596                        });
1597                }
1598            }
1599
1600            children.push(MeasuredChild {
1601                node: child,
1602                offset: position,
1603            });
1604        }
1605
1606        // Update the SubcomposeLayoutNode's active children for rendering
1607        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1608        node_handle.recycle_placement_scratch(placements);
1609
1610        Ok(Some(Rc::new(MeasuredNode::new(
1611            node_id,
1612            Size { width, height },
1613            offset,
1614            Point::default(), // Subcompose nodes: content_offset handled by child layout
1615            children,
1616        ))))
1617    }
1618    /// Measures through the layout modifier coordinator chain using reconciled modifier nodes.
1619    /// Iterates through LayoutModifierNode instances from the ModifierNodeChain and calls
1620    /// their measure() methods through the retained coordinator chain.
1621    ///
1622    /// Always succeeds, measuring either directly or through retained layout modifier nodes.
1623    ///
1624    fn measure_through_modifier_chain(
1625        state_rc: &Rc<RefCell<Self>>,
1626        node_id: NodeId,
1627        runtime_state: &mut LayoutRuntimeState,
1628        measure_policy: &Rc<dyn MeasurePolicy>,
1629        constraints: Constraints,
1630        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1631        placements: &mut Vec<Placement>,
1632    ) -> ModifierChainMeasurement {
1633        use cranpose_foundation::NodeCapabilities;
1634
1635        // Collect layout node information from the modifier chain
1636        layout_node_data.clear();
1637        let mut offset = Point::default();
1638
1639        {
1640            let state = state_rc.borrow();
1641            let mut applier = state.applier.borrow_typed();
1642
1643            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1644                let chain_handle = layout_node.modifier_chain();
1645
1646                if !chain_handle.has_layout_nodes() {
1647                    return;
1648                }
1649
1650                // Collect indices and node Rc clones for layout modifier nodes
1651                chain_handle.chain().for_each_forward_matching(
1652                    NodeCapabilities::LAYOUT,
1653                    |node_ref| {
1654                        if let Some(index) = node_ref.entry_index() {
1655                            // Get the Rc clone for this node
1656                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1657                                layout_node_data.push((index, Rc::clone(&node_rc)));
1658                            }
1659
1660                            // Extract offset from OffsetNode for the node's own position
1661                            // The coordinator chain handles placement_offset (for children),
1662                            // but the node's offset affects where IT is positioned in the parent
1663                            node_ref.with_node(|node| {
1664                                if let Some(offset_node) =
1665                                    node.as_any()
1666                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1667                                {
1668                                    let delta = offset_node.offset();
1669                                    offset.x += delta.x;
1670                                    offset.y += delta.y;
1671                                }
1672                            });
1673                        }
1674                    },
1675                );
1676            });
1677        }
1678
1679        // Fast path: if there are no layout modifiers, measure directly without the
1680        // retained coordinator chain frame.
1681        if layout_node_data.is_empty() {
1682            let final_size = measure_policy.measure_into(
1683                runtime_state.child_measurables(),
1684                constraints,
1685                placements,
1686            );
1687
1688            return ModifierChainMeasurement {
1689                size: final_size,
1690                content_offset: Point::default(),
1691                offset,
1692            };
1693        }
1694
1695        runtime_state.reconcile_coordinator_chain(layout_node_data.as_slice());
1696        let frame = CoordinatorFrame::new(
1697            measure_policy,
1698            runtime_state.child_measurables(),
1699            placements,
1700        );
1701
1702        // Measure through the complete coordinator chain
1703        let placeable = runtime_state
1704            .coordinator_chain()
1705            .measure_from(0, &frame, constraints);
1706        let final_size = Size {
1707            width: placeable.width(),
1708            height: placeable.height(),
1709        };
1710
1711        // Get accumulated content offset from the placeable (computed during measure)
1712        let content_offset = placeable.content_offset();
1713        let all_placement_offset = Point {
1714            x: content_offset.0,
1715            y: content_offset.1,
1716        };
1717
1718        // The content_offset for scroll/inner transforms is the accumulated placement offset
1719        // MINUS the node's own offset (which affects its position in the parent, not content position).
1720        // This properly separates: node position (offset) vs inner content position (content_offset).
1721        let content_offset = Point {
1722            x: all_placement_offset.x - offset.x,
1723            y: all_placement_offset.y - offset.y,
1724        };
1725
1726        // offset was already extracted from OffsetNode above
1727
1728        // Process any invalidations requested during measurement
1729        let invalidations = frame.take_invalidations();
1730        if !invalidations.is_empty() {
1731            // Mark the LayoutNode as needing the appropriate passes
1732            Self::with_applier_result(state_rc, |applier| {
1733                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1734                    for kind in invalidations {
1735                        match kind {
1736                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1737                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1738                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1739                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1740                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1741                        }
1742                    }
1743                })
1744            })
1745            .ok();
1746        }
1747
1748        ModifierChainMeasurement {
1749            size: final_size,
1750            content_offset,
1751            offset,
1752        }
1753    }
1754
1755    fn layout_child_measure_data(
1756        applier: &mut MemoryApplier,
1757        child_id: NodeId,
1758    ) -> Result<Option<LayoutChildMeasureData>, NodeError> {
1759        match applier.with_node::<LayoutNode, _>(child_id, |n| LayoutChildMeasureData {
1760            cache: n.cache_handles(),
1761            layout_state: Some(n.layout_state_handle()),
1762            needs_layout: n.needs_layout(),
1763            needs_measure: n.needs_measure(),
1764        }) {
1765            Ok(data) => Ok(Some(data)),
1766            Err(NodeError::TypeMismatch { .. }) => {
1767                match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |n| {
1768                    LayoutChildMeasureData {
1769                        cache: n.cache_handles(),
1770                        layout_state: None,
1771                        needs_layout: n.needs_layout(),
1772                        needs_measure: n.needs_measure(),
1773                    }
1774                }) {
1775                    Ok(data) => Ok(Some(data)),
1776                    Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
1777                        Ok(None)
1778                    }
1779                    Err(err) => Err(err),
1780                }
1781            }
1782            Err(NodeError::Missing { .. }) => Ok(None),
1783            Err(err) => Err(err),
1784        }
1785    }
1786
1787    fn measure_layout_node(
1788        state_rc: Rc<RefCell<Self>>,
1789        node_id: NodeId,
1790        snapshot: LayoutNodeSnapshot,
1791        constraints: Constraints,
1792    ) -> Result<Rc<MeasuredNode>, NodeError> {
1793        let cache_epoch = {
1794            let state = state_rc.borrow();
1795            state.cache_epoch
1796        };
1797        let LayoutNodeSnapshot {
1798            measure_policy,
1799            cache,
1800            layout_runtime_state,
1801            needs_layout,
1802            needs_measure,
1803        } = snapshot;
1804        cache.activate(cache_epoch);
1805
1806        if needs_measure {
1807            // Node has needs_measure=true
1808        }
1809
1810        // Only check cache when the node is fully clean.
1811        // needs_layout=true means either the node itself or one of its descendants
1812        // must be revisited even if the node's own measured size can stay cached.
1813        if !needs_measure && !needs_layout {
1814            // Check cache for current constraints
1815            if let Some(cached) = cache.get_measurement(constraints) {
1816                // Clear dirty flag after successful cache hit
1817                Self::with_applier_result(&state_rc, |applier| {
1818                    applier.with_node::<LayoutNode, _>(node_id, |node| {
1819                        node.clear_needs_measure();
1820                        node.clear_needs_layout();
1821                    })
1822                })
1823                .ok();
1824                return Ok(cached);
1825            }
1826        }
1827
1828        let (runtime_handle, applier_host) = {
1829            let state = state_rc.borrow();
1830            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1831        };
1832
1833        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1834        let error = Rc::new(RefCell::new(None));
1835        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1836        let (records, child_ids, layout_node_data, placements) = pools.parts();
1837
1838        applier_host
1839            .borrow_typed()
1840            .with_node::<LayoutNode, _>(node_id, |node| {
1841                child_ids.extend_from_slice(&node.children);
1842            })?;
1843
1844        let mut valid_child_count = 0;
1845        for index in 0..child_ids.len() {
1846            let child_id = child_ids[index];
1847            let child_exists = {
1848                let mut applier = applier_host.borrow_typed();
1849                Self::layout_child_measure_data(&mut applier, child_id)?.is_some()
1850            };
1851            if child_exists {
1852                child_ids[valid_child_count] = child_id;
1853                valid_child_count += 1;
1854            }
1855        }
1856        child_ids.truncate(valid_child_count);
1857
1858        let _frame_binding_cleanup =
1859            LayoutRuntimeFrameBindingCleanup::new(Rc::clone(&layout_runtime_state));
1860
1861        {
1862            let mut runtime_state = layout_runtime_state.borrow_mut();
1863            runtime_state.reconcile_child_measurables(child_ids.as_slice());
1864
1865            for (index, &child_id) in child_ids.iter().enumerate() {
1866                let data = {
1867                    let mut applier = applier_host.borrow_typed();
1868                    Self::layout_child_measure_data(&mut applier, child_id)?
1869                };
1870                let Some(data) = data else {
1871                    continue;
1872                };
1873
1874                let child_is_dirty = data.needs_layout || data.needs_measure;
1875                let child_cache_epoch = if child_is_dirty {
1876                    cache_epoch
1877                } else {
1878                    data.cache.epoch()
1879                };
1880                let child_state = runtime_state.child_state(index);
1881                child_state.configure(LayoutChildMeasureConfig {
1882                    applier: Rc::clone(&applier_host),
1883                    node_id: child_id,
1884                    error: Rc::clone(&error),
1885                    runtime_handle: runtime_handle.clone(),
1886                    cache: data.cache,
1887                    cache_epoch: child_cache_epoch,
1888                    force_remeasure: child_is_dirty,
1889                    measure_handle: Some(measure_handle.clone()),
1890                    layout_state: data.layout_state,
1891                });
1892                records.push((child_id, ChildRecord { state: child_state }));
1893            }
1894        }
1895
1896        let chain_constraints = constraints;
1897
1898        let modifier_chain_result = {
1899            let mut runtime_state = layout_runtime_state.borrow_mut();
1900            Self::measure_through_modifier_chain(
1901                &state_rc,
1902                node_id,
1903                &mut runtime_state,
1904                &measure_policy,
1905                chain_constraints,
1906                layout_node_data,
1907                placements,
1908            )
1909        };
1910
1911        // Modifier chain always succeeds - use the node-driven measurement.
1912        let (width, height, content_offset, offset) = {
1913            let result = modifier_chain_result;
1914            // The size is already correct from the modifier chain (modifiers like SizeNode
1915            // have already enforced their constraints), so we use it directly.
1916            if let Some(err) = error.borrow_mut().take() {
1917                return Err(err);
1918            }
1919
1920            (
1921                result.size.width,
1922                result.size.height,
1923                result.content_offset,
1924                result.offset,
1925            )
1926        };
1927
1928        let mut measured_children = Vec::with_capacity(records.len());
1929        for (child_id, record) in records.iter() {
1930            if let Some(measured) = record.state.take_measured() {
1931                let base_position = placements
1932                    .iter()
1933                    .find(|placement| placement.node_id == *child_id)
1934                    .map(|placement| Point {
1935                        x: placement.x,
1936                        y: placement.y,
1937                    })
1938                    .or_else(|| record.state.last_position())
1939                    .unwrap_or(Point { x: 0.0, y: 0.0 });
1940                // Apply content_offset (from scroll/transforms) to child positioning
1941                let position = Point {
1942                    x: content_offset.x + base_position.x,
1943                    y: content_offset.y + base_position.y,
1944                };
1945                measured_children.push(MeasuredChild {
1946                    node: measured,
1947                    offset: position,
1948                });
1949            }
1950        }
1951
1952        let measured = Rc::new(MeasuredNode::new(
1953            node_id,
1954            Size { width, height },
1955            offset,
1956            content_offset,
1957            measured_children,
1958        ));
1959
1960        cache.store_measurement(constraints, Rc::clone(&measured));
1961
1962        // Clear dirty flags and update derived state
1963        Self::with_applier_result(&state_rc, |applier| {
1964            applier.with_node::<LayoutNode, _>(node_id, |node| {
1965                node.clear_needs_measure();
1966                node.clear_needs_layout();
1967                node.set_measured_size(Size { width, height });
1968                node.set_content_offset(content_offset);
1969            })
1970        })
1971        .ok();
1972
1973        Ok(measured)
1974    }
1975}
1976
1977struct LayoutChildMeasureData {
1978    cache: LayoutNodeCacheHandles,
1979    layout_state: Option<Rc<RefCell<LayoutState>>>,
1980    needs_layout: bool,
1981    needs_measure: bool,
1982}
1983
1984/// Snapshot of a LayoutNode's data for measuring.
1985/// This is a temporary copy used during the measure phase, not a live node.
1986///
1987/// Note: We capture `needs_measure` here because it's checked during measure to enable
1988/// selective measure optimization at the individual node level. Even if the tree is partially
1989/// dirty (some nodes changed), clean nodes can skip measure and use cached results.
1990struct LayoutNodeSnapshot {
1991    measure_policy: Rc<dyn MeasurePolicy>,
1992    cache: LayoutNodeCacheHandles,
1993    layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
1994    needs_layout: bool,
1995    /// Whether this specific node needs to be measured (vs using cached measurement)
1996    needs_measure: bool,
1997}
1998
1999impl LayoutNodeSnapshot {
2000    fn from_layout_node(node: &LayoutNode) -> Self {
2001        Self {
2002            measure_policy: Rc::clone(&node.measure_policy),
2003            cache: node.cache_handles(),
2004            layout_runtime_state: node.layout_runtime_state_handle(),
2005            needs_layout: node.needs_layout(),
2006            needs_measure: node.needs_measure(),
2007        }
2008    }
2009}
2010
2011// Helper types for accessing subsets of LayoutBuilderState
2012struct VecPools {
2013    state: Rc<RefCell<LayoutBuilderState>>,
2014    records: Vec<(NodeId, ChildRecord)>,
2015    child_ids: Vec<NodeId>,
2016    layout_node_data: Vec<LayoutModifierNodeData>,
2017    placements: Vec<Placement>,
2018}
2019
2020impl VecPools {
2021    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2022        let (records, child_ids, layout_node_data, placements) = {
2023            let mut state_mut = state.borrow_mut();
2024            (
2025                state_mut.frame_arena.tmp_records.acquire(),
2026                state_mut.frame_arena.tmp_child_ids.acquire(),
2027                state_mut.frame_arena.tmp_layout_node_data.acquire(),
2028                state_mut.frame_arena.tmp_placements.acquire(),
2029            )
2030        };
2031        Self {
2032            state,
2033            records,
2034            child_ids,
2035            layout_node_data,
2036            placements,
2037        }
2038    }
2039
2040    #[allow(clippy::type_complexity)] // Returns internal Vec references for layout operations
2041    fn parts(
2042        &mut self,
2043    ) -> (
2044        &mut Vec<(NodeId, ChildRecord)>,
2045        &mut Vec<NodeId>,
2046        &mut Vec<LayoutModifierNodeData>,
2047        &mut Vec<Placement>,
2048    ) {
2049        (
2050            &mut self.records,
2051            &mut self.child_ids,
2052            &mut self.layout_node_data,
2053            &mut self.placements,
2054        )
2055    }
2056}
2057
2058impl Drop for VecPools {
2059    fn drop(&mut self) {
2060        let mut state = self.state.borrow_mut();
2061        state
2062            .frame_arena
2063            .tmp_records
2064            .release(std::mem::take(&mut self.records));
2065        state
2066            .frame_arena
2067            .tmp_child_ids
2068            .release(std::mem::take(&mut self.child_ids));
2069        state
2070            .frame_arena
2071            .tmp_layout_node_data
2072            .release(std::mem::take(&mut self.layout_node_data));
2073        state
2074            .frame_arena
2075            .tmp_placements
2076            .release(std::mem::take(&mut self.placements));
2077    }
2078}
2079
2080struct SlotsGuard {
2081    state: Rc<RefCell<LayoutBuilderState>>,
2082    slots: Option<SlotTable>,
2083}
2084
2085impl SlotsGuard {
2086    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2087        let slots = {
2088            let state_ref = state.borrow();
2089            let mut slots_ref = state_ref.slots.borrow_mut();
2090            std::mem::take(&mut *slots_ref)
2091        };
2092        Self {
2093            state,
2094            slots: Some(slots),
2095        }
2096    }
2097
2098    fn host(&mut self) -> Rc<SlotsHost> {
2099        let slots = self.slots.take().unwrap_or_default();
2100        Rc::new(SlotsHost::new(slots))
2101    }
2102
2103    fn restore(&mut self, slots: SlotTable) {
2104        debug_assert!(self.slots.is_none());
2105        self.slots = Some(slots);
2106    }
2107}
2108
2109impl Drop for SlotsGuard {
2110    fn drop(&mut self) {
2111        if let Some(slots) = self.slots.take() {
2112            let state_ref = self.state.borrow();
2113            *state_ref.slots.borrow_mut() = slots;
2114        }
2115    }
2116}
2117
2118#[derive(Clone)]
2119struct LayoutMeasureHandle {
2120    state: Rc<RefCell<LayoutBuilderState>>,
2121}
2122
2123impl LayoutMeasureHandle {
2124    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2125        Self { state }
2126    }
2127
2128    fn measure(
2129        &self,
2130        node_id: NodeId,
2131        constraints: Constraints,
2132    ) -> Result<Rc<MeasuredNode>, NodeError> {
2133        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
2134    }
2135}
2136
2137#[derive(Debug, Clone)]
2138pub(crate) struct MeasuredNode {
2139    node_id: NodeId,
2140    size: Size,
2141    /// Node's position offset relative to parent (from OffsetNode etc.)
2142    offset: Point,
2143    /// Content offset for scroll/inner transforms (NOT node position)
2144    content_offset: Point,
2145    children: Vec<MeasuredChild>,
2146}
2147
2148impl MeasuredNode {
2149    fn new(
2150        node_id: NodeId,
2151        size: Size,
2152        offset: Point,
2153        content_offset: Point,
2154        children: Vec<MeasuredChild>,
2155    ) -> Self {
2156        Self {
2157            node_id,
2158            size,
2159            offset,
2160            content_offset,
2161            children,
2162        }
2163    }
2164
2165    #[cfg(test)]
2166    pub(crate) fn leaf(node_id: NodeId, size: Size) -> Self {
2167        Self::new(
2168            node_id,
2169            size,
2170            Point::default(),
2171            Point::default(),
2172            Vec::new(),
2173        )
2174    }
2175
2176    pub(crate) fn node_id(&self) -> NodeId {
2177        self.node_id
2178    }
2179
2180    pub(crate) fn size(&self) -> Size {
2181        self.size
2182    }
2183}
2184
2185#[derive(Debug, Clone)]
2186struct MeasuredChild {
2187    node: Rc<MeasuredNode>,
2188    offset: Point,
2189}
2190
2191struct ChildRecord {
2192    state: Rc<LayoutChildMeasureState>,
2193}
2194
2195struct CoordinatorFrame<'a> {
2196    measure_policy: &'a Rc<dyn MeasurePolicy>,
2197    measurables: &'a [Box<dyn Measurable>],
2198    placements: RefCell<&'a mut Vec<Placement>>,
2199    context: RefCell<LayoutNodeContext>,
2200}
2201
2202impl<'a> CoordinatorFrame<'a> {
2203    fn new(
2204        measure_policy: &'a Rc<dyn MeasurePolicy>,
2205        measurables: &'a [Box<dyn Measurable>],
2206        placements: &'a mut Vec<Placement>,
2207    ) -> Self {
2208        Self {
2209            measure_policy,
2210            measurables,
2211            placements: RefCell::new(placements),
2212            context: RefCell::new(LayoutNodeContext::new()),
2213        }
2214    }
2215
2216    fn take_invalidations(&self) -> Vec<InvalidationKind> {
2217        self.context.borrow_mut().take_invalidations()
2218    }
2219}
2220
2221struct CoordinatorLink<'chain, 'frame_ref, 'frame_data> {
2222    chain: &'chain CoordinatorChain,
2223    frame: &'frame_ref CoordinatorFrame<'frame_data>,
2224    index: usize,
2225}
2226
2227impl Measurable for CoordinatorLink<'_, '_, '_> {
2228    fn measure(&self, constraints: Constraints) -> Placeable {
2229        self.chain.measure_from(self.index, self.frame, constraints)
2230    }
2231
2232    fn min_intrinsic_width(&self, height: f32) -> f32 {
2233        self.chain
2234            .min_intrinsic_width_from(self.index, self.frame, height)
2235    }
2236
2237    fn max_intrinsic_width(&self, height: f32) -> f32 {
2238        self.chain
2239            .max_intrinsic_width_from(self.index, self.frame, height)
2240    }
2241
2242    fn min_intrinsic_height(&self, width: f32) -> f32 {
2243        self.chain
2244            .min_intrinsic_height_from(self.index, self.frame, width)
2245    }
2246
2247    fn max_intrinsic_height(&self, width: f32) -> f32 {
2248        self.chain
2249            .max_intrinsic_height_from(self.index, self.frame, width)
2250    }
2251}
2252
2253struct CoordinatorNode {
2254    modifier_index: usize,
2255    node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2256    measured_size: Cell<Size>,
2257    accumulated_offset: Cell<Point>,
2258}
2259
2260impl CoordinatorNode {
2261    fn new(
2262        modifier_index: usize,
2263        node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2264    ) -> Self {
2265        Self {
2266            modifier_index,
2267            node,
2268            measured_size: Cell::new(Size::default()),
2269            accumulated_offset: Cell::new(Point::default()),
2270        }
2271    }
2272
2273    fn matches(
2274        &self,
2275        modifier_index: usize,
2276        node: &Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2277    ) -> bool {
2278        self.modifier_index == modifier_index && Rc::ptr_eq(&self.node, node)
2279    }
2280
2281    #[cfg(test)]
2282    fn ptr(&self) -> usize {
2283        Rc::as_ptr(&self.node) as *const () as usize
2284    }
2285}
2286
2287#[derive(Default)]
2288struct CoordinatorChain {
2289    nodes: Vec<CoordinatorNode>,
2290}
2291
2292impl CoordinatorChain {
2293    fn reconcile(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2294        if self.matches(layout_node_data) {
2295            return;
2296        }
2297
2298        let mut previous_nodes = std::mem::take(&mut self.nodes);
2299        self.nodes.reserve(layout_node_data.len());
2300
2301        for (modifier_index, node) in layout_node_data.iter() {
2302            if let Some(position) = previous_nodes
2303                .iter()
2304                .position(|candidate| candidate.matches(*modifier_index, node))
2305            {
2306                self.nodes.push(previous_nodes.swap_remove(position));
2307            } else {
2308                self.nodes
2309                    .push(CoordinatorNode::new(*modifier_index, Rc::clone(node)));
2310            }
2311        }
2312    }
2313
2314    fn matches(&self, layout_node_data: &[LayoutModifierNodeData]) -> bool {
2315        self.nodes.len() == layout_node_data.len()
2316            && self
2317                .nodes
2318                .iter()
2319                .zip(layout_node_data.iter())
2320                .all(|(node, (modifier_index, node_rc))| node.matches(*modifier_index, node_rc))
2321    }
2322
2323    fn measure_from(
2324        &self,
2325        index: usize,
2326        frame: &CoordinatorFrame<'_>,
2327        constraints: Constraints,
2328    ) -> Placeable {
2329        let Some(node) = self.nodes.get(index) else {
2330            let mut placements = frame.placements.borrow_mut();
2331            let size =
2332                frame
2333                    .measure_policy
2334                    .measure_into(frame.measurables, constraints, &mut placements);
2335            return Placeable::value(size.width, size.height, NodeId::default());
2336        };
2337
2338        let wrapped = CoordinatorLink {
2339            chain: self,
2340            frame,
2341            index: index + 1,
2342        };
2343        let node_borrow = node.node.borrow();
2344
2345        let Some(layout_node) = node_borrow.as_layout_node() else {
2346            let placeable = wrapped.measure(constraints);
2347            let child_accumulated = self.total_content_offset_from(index + 1);
2348            node.accumulated_offset.set(child_accumulated);
2349            return Placeable::value_with_offset(
2350                placeable.width(),
2351                placeable.height(),
2352                NodeId::default(),
2353                (child_accumulated.x, child_accumulated.y),
2354            );
2355        };
2356
2357        let result = match frame.context.try_borrow_mut() {
2358            Ok(mut context) => layout_node.measure(&mut *context, &wrapped, constraints),
2359            Err(_) => {
2360                let mut temp = LayoutNodeContext::new();
2361                let result = layout_node.measure(&mut temp, &wrapped, constraints);
2362                if let Ok(mut context) = frame.context.try_borrow_mut() {
2363                    for kind in temp.take_invalidations() {
2364                        context.invalidate(kind);
2365                    }
2366                }
2367                result
2368            }
2369        };
2370
2371        node.measured_size.set(result.size);
2372        let local_offset = Point {
2373            x: result.placement_offset_x,
2374            y: result.placement_offset_y,
2375        };
2376        let child_accumulated = self.total_content_offset_from(index + 1);
2377        let accumulated = Point {
2378            x: local_offset.x + child_accumulated.x,
2379            y: local_offset.y + child_accumulated.y,
2380        };
2381        node.accumulated_offset.set(accumulated);
2382
2383        Placeable::value_with_offset(
2384            result.size.width,
2385            result.size.height,
2386            NodeId::default(),
2387            (accumulated.x, accumulated.y),
2388        )
2389    }
2390
2391    fn min_intrinsic_width_from(
2392        &self,
2393        index: usize,
2394        frame: &CoordinatorFrame<'_>,
2395        height: f32,
2396    ) -> f32 {
2397        let Some(node) = self.nodes.get(index) else {
2398            return frame
2399                .measure_policy
2400                .min_intrinsic_width(frame.measurables, height);
2401        };
2402        let wrapped = CoordinatorLink {
2403            chain: self,
2404            frame,
2405            index: index + 1,
2406        };
2407        let node_borrow = node.node.borrow();
2408        node_borrow
2409            .as_layout_node()
2410            .map(|layout_node| layout_node.min_intrinsic_width(&wrapped, height))
2411            .unwrap_or_else(|| wrapped.min_intrinsic_width(height))
2412    }
2413
2414    fn max_intrinsic_width_from(
2415        &self,
2416        index: usize,
2417        frame: &CoordinatorFrame<'_>,
2418        height: f32,
2419    ) -> f32 {
2420        let Some(node) = self.nodes.get(index) else {
2421            return frame
2422                .measure_policy
2423                .max_intrinsic_width(frame.measurables, height);
2424        };
2425        let wrapped = CoordinatorLink {
2426            chain: self,
2427            frame,
2428            index: index + 1,
2429        };
2430        let node_borrow = node.node.borrow();
2431        node_borrow
2432            .as_layout_node()
2433            .map(|layout_node| layout_node.max_intrinsic_width(&wrapped, height))
2434            .unwrap_or_else(|| wrapped.max_intrinsic_width(height))
2435    }
2436
2437    fn min_intrinsic_height_from(
2438        &self,
2439        index: usize,
2440        frame: &CoordinatorFrame<'_>,
2441        width: f32,
2442    ) -> f32 {
2443        let Some(node) = self.nodes.get(index) else {
2444            return frame
2445                .measure_policy
2446                .min_intrinsic_height(frame.measurables, width);
2447        };
2448        let wrapped = CoordinatorLink {
2449            chain: self,
2450            frame,
2451            index: index + 1,
2452        };
2453        let node_borrow = node.node.borrow();
2454        node_borrow
2455            .as_layout_node()
2456            .map(|layout_node| layout_node.min_intrinsic_height(&wrapped, width))
2457            .unwrap_or_else(|| wrapped.min_intrinsic_height(width))
2458    }
2459
2460    fn max_intrinsic_height_from(
2461        &self,
2462        index: usize,
2463        frame: &CoordinatorFrame<'_>,
2464        width: f32,
2465    ) -> f32 {
2466        let Some(node) = self.nodes.get(index) else {
2467            return frame
2468                .measure_policy
2469                .max_intrinsic_height(frame.measurables, width);
2470        };
2471        let wrapped = CoordinatorLink {
2472            chain: self,
2473            frame,
2474            index: index + 1,
2475        };
2476        let node_borrow = node.node.borrow();
2477        node_borrow
2478            .as_layout_node()
2479            .map(|layout_node| layout_node.max_intrinsic_height(&wrapped, width))
2480            .unwrap_or_else(|| wrapped.max_intrinsic_height(width))
2481    }
2482
2483    fn total_content_offset_from(&self, index: usize) -> Point {
2484        self.nodes
2485            .get(index)
2486            .map(|node| node.accumulated_offset.get())
2487            .unwrap_or_default()
2488    }
2489
2490    #[cfg(test)]
2491    fn debug_ptrs(&self) -> Vec<usize> {
2492        self.nodes.iter().map(CoordinatorNode::ptr).collect()
2493    }
2494}
2495
2496#[derive(Default)]
2497pub(crate) struct LayoutRuntimeState {
2498    child_ids: Vec<NodeId>,
2499    child_states: Vec<Rc<LayoutChildMeasureState>>,
2500    child_measurables: Vec<Box<dyn Measurable>>,
2501    coordinator_chain: CoordinatorChain,
2502}
2503
2504impl LayoutRuntimeState {
2505    fn reconcile_child_measurables(&mut self, child_ids: &[NodeId]) {
2506        if self.child_ids == child_ids {
2507            return;
2508        }
2509
2510        let mut previous_ids = std::mem::take(&mut self.child_ids);
2511        let mut previous_states = std::mem::take(&mut self.child_states);
2512        let mut previous_measurables = std::mem::take(&mut self.child_measurables);
2513
2514        self.child_ids.reserve(child_ids.len());
2515        self.child_states.reserve(child_ids.len());
2516        self.child_measurables.reserve(child_ids.len());
2517
2518        for &child_id in child_ids {
2519            if let Some(position) = previous_ids.iter().position(|&id| id == child_id) {
2520                self.child_ids.push(previous_ids.swap_remove(position));
2521                self.child_states
2522                    .push(previous_states.swap_remove(position));
2523                self.child_measurables
2524                    .push(previous_measurables.swap_remove(position));
2525            } else {
2526                let state = LayoutChildMeasureState::new(child_id);
2527                self.child_ids.push(child_id);
2528                self.child_states.push(Rc::clone(&state));
2529                self.child_measurables
2530                    .push(Box::new(LayoutChildMeasurable::new(state)));
2531            }
2532        }
2533    }
2534
2535    fn child_state(&self, index: usize) -> Rc<LayoutChildMeasureState> {
2536        Rc::clone(&self.child_states[index])
2537    }
2538
2539    fn child_measurables(&self) -> &[Box<dyn Measurable>] {
2540        self.child_measurables.as_slice()
2541    }
2542
2543    fn reconcile_coordinator_chain(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2544        self.coordinator_chain.reconcile(layout_node_data);
2545    }
2546
2547    fn coordinator_chain(&self) -> &CoordinatorChain {
2548        &self.coordinator_chain
2549    }
2550
2551    fn clear_frame_bindings(&self) {
2552        for child_state in &self.child_states {
2553            child_state.clear_frame_bindings();
2554        }
2555    }
2556
2557    #[cfg(test)]
2558    pub(crate) fn debug_stats(&self) -> LayoutRuntimeDebugStats {
2559        LayoutRuntimeDebugStats {
2560            child_ids: self.child_ids.clone(),
2561            child_state_ptrs: self
2562                .child_states
2563                .iter()
2564                .map(|state| Rc::as_ptr(state) as *const () as usize)
2565                .collect(),
2566            child_measurable_ptrs: self
2567                .child_measurables
2568                .iter()
2569                .map(|measurable| {
2570                    measurable.as_ref() as *const dyn Measurable as *const () as usize
2571                })
2572                .collect(),
2573            child_measurable_count: self.child_measurables.len(),
2574            coordinator_node_ptrs: self.coordinator_chain.debug_ptrs(),
2575            coordinator_node_count: self.coordinator_chain.nodes.len(),
2576        }
2577    }
2578}
2579
2580#[cfg(test)]
2581#[derive(Debug, Clone, PartialEq, Eq)]
2582pub(crate) struct LayoutRuntimeDebugStats {
2583    pub(crate) child_ids: Vec<NodeId>,
2584    pub(crate) child_state_ptrs: Vec<usize>,
2585    pub(crate) child_measurable_ptrs: Vec<usize>,
2586    pub(crate) child_measurable_count: usize,
2587    pub(crate) coordinator_node_ptrs: Vec<usize>,
2588    pub(crate) coordinator_node_count: usize,
2589}
2590
2591struct LayoutChildMeasureConfig {
2592    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2593    node_id: NodeId,
2594    error: Rc<RefCell<Option<NodeError>>>,
2595    runtime_handle: Option<RuntimeHandle>,
2596    cache: LayoutNodeCacheHandles,
2597    cache_epoch: u64,
2598    force_remeasure: bool,
2599    measure_handle: Option<LayoutMeasureHandle>,
2600    layout_state: Option<Rc<RefCell<LayoutState>>>,
2601}
2602
2603struct LayoutChildMeasureState {
2604    applier: RefCell<Option<Rc<ConcreteApplierHost<MemoryApplier>>>>,
2605    node_id: Cell<NodeId>,
2606    measured: RefCell<Option<Rc<MeasuredNode>>>,
2607    last_position: Cell<Option<Point>>,
2608    error: RefCell<Option<Rc<RefCell<Option<NodeError>>>>>,
2609    runtime_handle: RefCell<Option<RuntimeHandle>>,
2610    cache: RefCell<LayoutNodeCacheHandles>,
2611    cache_epoch: Cell<u64>,
2612    force_remeasure: Cell<bool>,
2613    measure_handle: RefCell<Option<LayoutMeasureHandle>>,
2614    layout_state: RefCell<Option<Rc<RefCell<LayoutState>>>>,
2615}
2616
2617impl LayoutChildMeasureState {
2618    fn new(node_id: NodeId) -> Rc<Self> {
2619        Rc::new(Self {
2620            applier: RefCell::new(None),
2621            node_id: Cell::new(node_id),
2622            measured: RefCell::new(None),
2623            last_position: Cell::new(None),
2624            error: RefCell::new(None),
2625            runtime_handle: RefCell::new(None),
2626            cache: RefCell::new(LayoutNodeCacheHandles::default()),
2627            cache_epoch: Cell::new(0),
2628            force_remeasure: Cell::new(true),
2629            measure_handle: RefCell::new(None),
2630            layout_state: RefCell::new(None),
2631        })
2632    }
2633
2634    fn configure(&self, config: LayoutChildMeasureConfig) {
2635        config.cache.activate(config.cache_epoch);
2636        *self.applier.borrow_mut() = Some(config.applier);
2637        self.node_id.set(config.node_id);
2638        self.measured.borrow_mut().take();
2639        self.last_position.set(None);
2640        *self.error.borrow_mut() = Some(config.error);
2641        *self.runtime_handle.borrow_mut() = config.runtime_handle;
2642        *self.cache.borrow_mut() = config.cache;
2643        self.cache_epoch.set(config.cache_epoch);
2644        self.force_remeasure.set(config.force_remeasure);
2645        *self.measure_handle.borrow_mut() = config.measure_handle;
2646        *self.layout_state.borrow_mut() = config.layout_state;
2647    }
2648
2649    fn clear_frame_bindings(&self) {
2650        self.measured.borrow_mut().take();
2651        *self.applier.borrow_mut() = None;
2652        *self.error.borrow_mut() = None;
2653        *self.runtime_handle.borrow_mut() = None;
2654        *self.measure_handle.borrow_mut() = None;
2655        *self.layout_state.borrow_mut() = None;
2656    }
2657
2658    fn node_id(&self) -> NodeId {
2659        self.node_id.get()
2660    }
2661
2662    fn cache(&self) -> LayoutNodeCacheHandles {
2663        self.cache.borrow().clone()
2664    }
2665
2666    fn applier(&self) -> Option<Rc<ConcreteApplierHost<MemoryApplier>>> {
2667        self.applier.borrow().clone()
2668    }
2669
2670    fn layout_state(&self) -> Option<Rc<RefCell<LayoutState>>> {
2671        self.layout_state.borrow().clone()
2672    }
2673
2674    fn take_measured(&self) -> Option<Rc<MeasuredNode>> {
2675        self.measured.borrow_mut().take()
2676    }
2677
2678    fn last_position(&self) -> Option<Point> {
2679        self.last_position.get()
2680    }
2681
2682    fn set_last_position(&self, position: Point) {
2683        self.last_position.set(Some(position));
2684    }
2685
2686    fn set_measured(&self, measured: Option<Rc<MeasuredNode>>) {
2687        *self.measured.borrow_mut() = measured;
2688    }
2689
2690    fn record_error(&self, err: NodeError) {
2691        let Some(error) = self.error.borrow().clone() else {
2692            return;
2693        };
2694        let mut slot = error.borrow_mut();
2695        if slot.is_none() {
2696            *slot = Some(err);
2697        }
2698    }
2699
2700    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
2701        let node_id = self.node_id();
2702        if let Some(handle) = self.measure_handle.borrow().clone() {
2703            return handle.measure(node_id, constraints);
2704        }
2705        let applier = self.applier().ok_or(NodeError::MissingContext {
2706            id: node_id,
2707            reason: "layout child applier not configured",
2708        })?;
2709        measure_node_with_host(
2710            applier,
2711            self.runtime_handle.borrow().clone(),
2712            node_id,
2713            constraints,
2714            self.cache_epoch.get(),
2715        )
2716    }
2717
2718    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
2719        let cache = self.cache();
2720        cache.activate(self.cache_epoch.get());
2721        if !self.force_remeasure.get() {
2722            if let Some(cached) = cache.get_measurement(constraints) {
2723                return Some(cached);
2724            }
2725        }
2726
2727        match self.perform_measure(constraints) {
2728            Ok(measured) => {
2729                self.force_remeasure.set(false);
2730                cache.store_measurement(constraints, Rc::clone(&measured));
2731                Some(measured)
2732            }
2733            Err(err) => {
2734                self.record_error(err);
2735                None
2736            }
2737        }
2738    }
2739}
2740
2741struct LayoutChildMeasurable {
2742    state: Rc<LayoutChildMeasureState>,
2743}
2744
2745impl LayoutChildMeasurable {
2746    fn new(state: Rc<LayoutChildMeasureState>) -> Self {
2747        Self { state }
2748    }
2749}
2750
2751impl Measurable for LayoutChildMeasurable {
2752    fn measure(&self, constraints: Constraints) -> Placeable {
2753        let state = &self.state;
2754        let cache = state.cache();
2755        cache.activate(state.cache_epoch.get());
2756        let measured_size;
2757        if !state.force_remeasure.get() {
2758            if let Some(cached) = cache.get_measurement(constraints) {
2759                measured_size = cached.size;
2760                state.set_measured(Some(Rc::clone(&cached)));
2761            } else {
2762                match state.perform_measure(constraints) {
2763                    Ok(measured) => {
2764                        state.force_remeasure.set(false);
2765                        measured_size = measured.size;
2766                        cache.store_measurement(constraints, Rc::clone(&measured));
2767                        state.set_measured(Some(measured));
2768                    }
2769                    Err(err) => {
2770                        state.record_error(err);
2771                        state.set_measured(None);
2772                        measured_size = Size {
2773                            width: 0.0,
2774                            height: 0.0,
2775                        };
2776                    }
2777                }
2778            }
2779        } else {
2780            match state.perform_measure(constraints) {
2781                Ok(measured) => {
2782                    state.force_remeasure.set(false);
2783                    measured_size = measured.size;
2784                    cache.store_measurement(constraints, Rc::clone(&measured));
2785                    state.set_measured(Some(measured));
2786                }
2787                Err(err) => {
2788                    state.record_error(err);
2789                    state.set_measured(None);
2790                    measured_size = Size {
2791                        width: 0.0,
2792                        height: 0.0,
2793                    };
2794                }
2795            }
2796        }
2797
2798        if let Some(layout_state) = state.layout_state() {
2799            let mut layout_state = layout_state.borrow_mut();
2800            layout_state.size = measured_size;
2801            layout_state.measurement_constraints = constraints;
2802        } else if let Some(applier) = state.applier() {
2803            let Ok(mut applier) = applier.try_borrow_typed() else {
2804                return Placeable::value(
2805                    measured_size.width,
2806                    measured_size.height,
2807                    state.node_id(),
2808                );
2809            };
2810            let _ = applier.with_node::<LayoutNode, _>(state.node_id(), |node| {
2811                node.set_measured_size(measured_size);
2812                node.set_measurement_constraints(constraints);
2813            });
2814        }
2815
2816        let state = Rc::clone(&self.state);
2817        let applier = state.applier();
2818        let node_id = state.node_id();
2819        let layout_state = state.layout_state();
2820
2821        let place_fn = Rc::new(move |x: f32, y: f32| {
2822            let internal_offset = state
2823                .measured
2824                .borrow()
2825                .as_ref()
2826                .map(|m| m.offset)
2827                .unwrap_or_default();
2828
2829            let position = Point {
2830                x: x + internal_offset.x,
2831                y: y + internal_offset.y,
2832            };
2833            state.set_last_position(position);
2834
2835            if let Some(layout_state) = &layout_state {
2836                let mut layout_state = layout_state.borrow_mut();
2837                layout_state.position = position;
2838                layout_state.is_placed = true;
2839            } else if let Some(applier) = &applier {
2840                let Ok(mut applier) = applier.try_borrow_typed() else {
2841                    return;
2842                };
2843                if applier
2844                    .with_node::<LayoutNode, _>(node_id, |node| {
2845                        node.set_position(position);
2846                    })
2847                    .is_err()
2848                {
2849                    let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2850                        node.set_position(position);
2851                    });
2852                }
2853            }
2854        });
2855
2856        Placeable::with_place_fn(measured_size.width, measured_size.height, node_id, place_fn)
2857    }
2858
2859    fn min_intrinsic_width(&self, height: f32) -> f32 {
2860        let kind = IntrinsicKind::MinWidth(height);
2861        let cache = self.state.cache();
2862        cache.activate(self.state.cache_epoch.get());
2863        if !self.state.force_remeasure.get() {
2864            if let Some(value) = cache.get_intrinsic(&kind) {
2865                return value;
2866            }
2867        }
2868        let constraints = Constraints {
2869            min_width: 0.0,
2870            max_width: f32::INFINITY,
2871            min_height: height,
2872            max_height: height,
2873        };
2874        if let Some(node) = self.state.intrinsic_measure(constraints) {
2875            let value = node.size.width;
2876            cache.store_intrinsic(kind, value);
2877            value
2878        } else {
2879            0.0
2880        }
2881    }
2882
2883    fn max_intrinsic_width(&self, height: f32) -> f32 {
2884        let kind = IntrinsicKind::MaxWidth(height);
2885        let cache = self.state.cache();
2886        cache.activate(self.state.cache_epoch.get());
2887        if !self.state.force_remeasure.get() {
2888            if let Some(value) = cache.get_intrinsic(&kind) {
2889                return value;
2890            }
2891        }
2892        let constraints = Constraints {
2893            min_width: 0.0,
2894            max_width: f32::INFINITY,
2895            min_height: 0.0,
2896            max_height: height,
2897        };
2898        if let Some(node) = self.state.intrinsic_measure(constraints) {
2899            let value = node.size.width;
2900            cache.store_intrinsic(kind, value);
2901            value
2902        } else {
2903            0.0
2904        }
2905    }
2906
2907    fn min_intrinsic_height(&self, width: f32) -> f32 {
2908        let kind = IntrinsicKind::MinHeight(width);
2909        let cache = self.state.cache();
2910        cache.activate(self.state.cache_epoch.get());
2911        if !self.state.force_remeasure.get() {
2912            if let Some(value) = cache.get_intrinsic(&kind) {
2913                return value;
2914            }
2915        }
2916        let constraints = Constraints {
2917            min_width: width,
2918            max_width: width,
2919            min_height: 0.0,
2920            max_height: f32::INFINITY,
2921        };
2922        if let Some(node) = self.state.intrinsic_measure(constraints) {
2923            let value = node.size.height;
2924            cache.store_intrinsic(kind, value);
2925            value
2926        } else {
2927            0.0
2928        }
2929    }
2930
2931    fn max_intrinsic_height(&self, width: f32) -> f32 {
2932        let kind = IntrinsicKind::MaxHeight(width);
2933        let cache = self.state.cache();
2934        cache.activate(self.state.cache_epoch.get());
2935        if !self.state.force_remeasure.get() {
2936            if let Some(value) = cache.get_intrinsic(&kind) {
2937                return value;
2938            }
2939        }
2940        let constraints = Constraints {
2941            min_width: 0.0,
2942            max_width: width,
2943            min_height: 0.0,
2944            max_height: f32::INFINITY,
2945        };
2946        if let Some(node) = self.state.intrinsic_measure(constraints) {
2947            let value = node.size.height;
2948            cache.store_intrinsic(kind, value);
2949            value
2950        } else {
2951            0.0
2952        }
2953    }
2954
2955    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
2956        let applier = self.state.applier()?;
2957        let node_id = self.state.node_id();
2958        let Ok(mut applier) = applier.try_borrow_typed() else {
2959            return None;
2960        };
2961
2962        applier
2963            .with_node::<LayoutNode, _>(node_id, |layout_node| {
2964                let props = layout_node.resolved_modifiers().layout_properties();
2965                props.weight().map(|weight_data| {
2966                    cranpose_ui_layout::FlexParentData::new(weight_data.weight, weight_data.fill)
2967                })
2968            })
2969            .ok()
2970            .flatten()
2971    }
2972}
2973
2974fn measure_node_with_host(
2975    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2976    runtime_handle: Option<RuntimeHandle>,
2977    node_id: NodeId,
2978    constraints: Constraints,
2979    epoch: u64,
2980) -> Result<Rc<MeasuredNode>, NodeError> {
2981    let runtime_handle = match runtime_handle {
2982        Some(handle) => Some(handle),
2983        None => applier.borrow_typed().runtime_handle(),
2984    };
2985    let mut builder = LayoutBuilder::new_with_epoch(
2986        applier,
2987        epoch,
2988        Rc::new(RefCell::new(SlotTable::default())),
2989        FrameLayoutArena::default(),
2990    );
2991    builder.set_runtime_handle(runtime_handle);
2992    builder.measure_node(node_id, constraints)
2993}
2994
2995#[derive(Clone)]
2996struct RuntimeNodeMetadata {
2997    modifier: Modifier,
2998    resolved_modifiers: ResolvedModifiers,
2999    modifier_slices: Rc<ModifierNodeSlices>,
3000    role: SemanticsRole,
3001    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
3002}
3003
3004impl Default for RuntimeNodeMetadata {
3005    fn default() -> Self {
3006        Self {
3007            modifier: Modifier::empty(),
3008            resolved_modifiers: ResolvedModifiers::default(),
3009            modifier_slices: Rc::default(),
3010            role: SemanticsRole::Unknown,
3011            button_handler: None,
3012        }
3013    }
3014}
3015
3016fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
3017    modifier_slices
3018        .text_content()
3019        .map(|text| SemanticsRole::Text {
3020            value: text.to_string(),
3021        })
3022        .unwrap_or(SemanticsRole::Layout)
3023}
3024
3025fn runtime_metadata_for(
3026    applier: &mut MemoryApplier,
3027    node_id: NodeId,
3028) -> Result<RuntimeNodeMetadata, NodeError> {
3029    // Try LayoutNode (the primary modern path)
3030    // IMPORTANT: We use with_node (reference) instead of try_clone because cloning
3031    // LayoutNode creates a NEW ModifierChainHandle with NEW nodes and NEW handlers,
3032    // which would lose gesture state like press_position.
3033    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
3034        let modifier = layout.modifier.clone();
3035        let resolved_modifiers = layout.resolved_modifiers();
3036        let modifier_slices = layout.modifier_slices_snapshot();
3037        let role = role_from_modifier_slices(&modifier_slices);
3038
3039        RuntimeNodeMetadata {
3040            modifier,
3041            resolved_modifiers,
3042            modifier_slices,
3043            role,
3044            button_handler: None,
3045        }
3046    }) {
3047        return Ok(meta);
3048    }
3049
3050    // Try SubcomposeLayoutNode
3051    if let Ok((modifier, resolved_modifiers, modifier_slices)) = applier
3052        .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
3053            (
3054                node.modifier(),
3055                node.resolved_modifiers(),
3056                node.modifier_slices_snapshot(),
3057            )
3058        })
3059    {
3060        return Ok(RuntimeNodeMetadata {
3061            modifier,
3062            resolved_modifiers,
3063            modifier_slices,
3064            role: SemanticsRole::Subcompose,
3065            button_handler: None,
3066        });
3067    }
3068    Ok(RuntimeNodeMetadata::default())
3069}
3070
3071fn clear_semantics_dirty_flags(
3072    applier: &mut MemoryApplier,
3073    node: &MeasuredNode,
3074) -> Result<(), NodeError> {
3075    match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3076        layout.clear_needs_semantics();
3077    }) {
3078        Ok(()) => {}
3079        Err(NodeError::Missing { .. }) => {}
3080        Err(NodeError::TypeMismatch { .. }) => {
3081            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3082                subcompose.clear_needs_semantics();
3083            }) {
3084                Ok(()) | Err(NodeError::Missing { .. }) | Err(NodeError::TypeMismatch { .. }) => {}
3085                Err(err) => return Err(err),
3086            }
3087        }
3088        Err(err) => return Err(err),
3089    }
3090
3091    for child in &node.children {
3092        clear_semantics_dirty_flags(applier, &child.node)?;
3093    }
3094
3095    Ok(())
3096}
3097
3098fn build_semantics_tree_from_live_nodes(
3099    applier: &mut MemoryApplier,
3100    node: &MeasuredNode,
3101) -> Result<SemanticsTree, NodeError> {
3102    Ok(SemanticsTree::new(build_semantics_node_from_live_nodes(
3103        applier, node,
3104    )?))
3105}
3106
3107fn semantics_node_from_parts(
3108    node_id: NodeId,
3109    mut role: SemanticsRole,
3110    config: Option<SemanticsConfiguration>,
3111    children: Vec<SemanticsNode>,
3112) -> SemanticsNode {
3113    let mut actions = Vec::new();
3114    let mut description = None;
3115    let mut editable_text = false;
3116    let mut text_selection = None;
3117
3118    if let Some(config) = config {
3119        if config.is_button {
3120            role = SemanticsRole::Button;
3121        }
3122        if config.is_clickable {
3123            actions.push(SemanticsAction::Click {
3124                handler: SemanticsCallback::new(node_id),
3125            });
3126        }
3127        description = config.content_description;
3128        editable_text = config.is_editable_text;
3129        text_selection = config.text_selection;
3130    }
3131
3132    SemanticsNode::new(
3133        node_id,
3134        role,
3135        actions,
3136        children,
3137        description,
3138        editable_text,
3139        text_selection,
3140    )
3141}
3142
3143fn build_semantics_node_from_live_nodes(
3144    applier: &mut MemoryApplier,
3145    node: &MeasuredNode,
3146) -> Result<SemanticsNode, NodeError> {
3147    let (role, config) = match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3148        let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
3149        let config = layout.semantics_configuration();
3150        layout.clear_needs_semantics();
3151        (role, config)
3152    }) {
3153        Ok(data) => data,
3154        Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
3155            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3156                subcompose.clear_needs_semantics();
3157                (
3158                    SemanticsRole::Subcompose,
3159                    collect_semantics_from_modifier(&subcompose.modifier()),
3160                )
3161            }) {
3162                Ok(data) => data,
3163                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
3164                    (SemanticsRole::Unknown, None)
3165                }
3166                Err(err) => return Err(err),
3167            }
3168        }
3169        Err(err) => return Err(err),
3170    };
3171
3172    let mut children = Vec::with_capacity(node.children.len());
3173    for child in &node.children {
3174        children.push(build_semantics_node_from_live_nodes(applier, &child.node)?);
3175    }
3176
3177    Ok(semantics_node_from_parts(
3178        node.node_id,
3179        role,
3180        config,
3181        children,
3182    ))
3183}
3184
3185fn record_semantics_allocation_stats(node: &SemanticsNode, stats: &mut LayoutAllocationDebugStats) {
3186    stats.semantics_node_count += 1;
3187    stats.semantics_action_count += node.actions.len();
3188    stats.semantics_action_capacity += node.actions.capacity();
3189    stats.semantics_child_count += node.children.len();
3190    stats.semantics_child_capacity += node.children.capacity();
3191    stats.semantics_heap_bytes += node.actions.capacity() * size_of::<SemanticsAction>();
3192    stats.semantics_heap_bytes += node.children.capacity() * size_of::<SemanticsNode>();
3193
3194    if let Some(description) = &node.description {
3195        stats.semantics_description_count += 1;
3196        stats.semantics_description_bytes += description.capacity();
3197        stats.semantics_heap_bytes += description.capacity();
3198    }
3199    if let SemanticsRole::Text { value } = &node.role {
3200        stats.semantics_text_role_bytes += value.capacity();
3201        stats.semantics_heap_bytes += value.capacity();
3202    }
3203
3204    for child in &node.children {
3205        record_semantics_allocation_stats(child, stats);
3206    }
3207}
3208
3209fn record_layout_box_allocation_stats(
3210    layout_box: &LayoutBox,
3211    stats: &mut LayoutAllocationDebugStats,
3212) {
3213    stats.layout_box_count += 1;
3214    stats.layout_box_child_count += layout_box.children.len();
3215    stats.layout_box_child_capacity += layout_box.children.capacity();
3216    stats.layout_box_heap_bytes += layout_box.children.capacity() * size_of::<LayoutBox>();
3217    stats.add_modifier_slice(layout_box.node_data.modifier_slices().debug_stats());
3218
3219    for child in &layout_box.children {
3220        record_layout_box_allocation_stats(child, stats);
3221    }
3222}
3223
3224fn build_layout_tree(
3225    applier: &mut MemoryApplier,
3226    node: &MeasuredNode,
3227) -> Result<LayoutTree, NodeError> {
3228    fn place(
3229        applier: &mut MemoryApplier,
3230        node: &MeasuredNode,
3231        origin: Point,
3232    ) -> Result<LayoutBox, NodeError> {
3233        // Include the node's own offset (from OffsetNode) in its position
3234        let top_left = Point {
3235            x: origin.x + node.offset.x,
3236            y: origin.y + node.offset.y,
3237        };
3238        let rect = GeometryRect {
3239            x: top_left.x,
3240            y: top_left.y,
3241            width: node.size.width,
3242            height: node.size.height,
3243        };
3244        let info = runtime_metadata_for(applier, node.node_id)?;
3245        let kind = layout_kind_from_metadata(node.node_id, &info);
3246        let RuntimeNodeMetadata {
3247            modifier,
3248            resolved_modifiers,
3249            modifier_slices,
3250            ..
3251        } = info;
3252        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
3253        let mut children = Vec::with_capacity(node.children.len());
3254        for child in &node.children {
3255            let child_origin = Point {
3256                x: top_left.x + child.offset.x,
3257                y: top_left.y + child.offset.y,
3258            };
3259            children.push(place(applier, &child.node, child_origin)?);
3260        }
3261        Ok(LayoutBox::new(
3262            node.node_id,
3263            rect,
3264            node.content_offset,
3265            data,
3266            children,
3267        ))
3268    }
3269
3270    Ok(LayoutTree::new(place(
3271        applier,
3272        node,
3273        Point { x: 0.0, y: 0.0 },
3274    )?))
3275}
3276
3277fn semantics_role_from_layout_box(layout_box: &LayoutBox) -> SemanticsRole {
3278    match &layout_box.node_data.kind {
3279        LayoutNodeKind::Subcompose => SemanticsRole::Subcompose,
3280        LayoutNodeKind::Spacer => SemanticsRole::Spacer,
3281        LayoutNodeKind::Unknown => SemanticsRole::Unknown,
3282        LayoutNodeKind::Button { .. } => SemanticsRole::Button,
3283        LayoutNodeKind::Layout => layout_box
3284            .node_data
3285            .modifier_slices()
3286            .text_content()
3287            .map(|text| SemanticsRole::Text {
3288                value: text.to_string(),
3289            })
3290            .unwrap_or(SemanticsRole::Layout),
3291    }
3292}
3293
3294fn build_semantics_node_from_layout_box(layout_box: &LayoutBox) -> SemanticsNode {
3295    let children = layout_box
3296        .children
3297        .iter()
3298        .map(build_semantics_node_from_layout_box)
3299        .collect();
3300
3301    semantics_node_from_parts(
3302        layout_box.node_id,
3303        semantics_role_from_layout_box(layout_box),
3304        collect_semantics_from_modifier(&layout_box.node_data.modifier),
3305        children,
3306    )
3307}
3308
3309fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
3310    match &info.role {
3311        SemanticsRole::Layout => LayoutNodeKind::Layout,
3312        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
3313        SemanticsRole::Text { .. } => {
3314            // Text content is now handled via TextModifierNode in the modifier chain
3315            // and collected in modifier_slices.text_content(). LayoutNodeKind should
3316            // reflect the layout policy (EmptyMeasurePolicy), not the content type.
3317            LayoutNodeKind::Layout
3318        }
3319        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
3320        SemanticsRole::Button => {
3321            let handler = info
3322                .button_handler
3323                .as_ref()
3324                .cloned()
3325                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
3326            LayoutNodeKind::Button { on_click: handler }
3327        }
3328        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
3329    }
3330}
3331
3332fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
3333    let horizontal = padding.horizontal_sum();
3334    let vertical = padding.vertical_sum();
3335    let min_width = (constraints.min_width - horizontal).max(0.0);
3336    let mut max_width = constraints.max_width;
3337    if max_width.is_finite() {
3338        max_width = (max_width - horizontal).max(0.0);
3339    }
3340    let min_height = (constraints.min_height - vertical).max(0.0);
3341    let mut max_height = constraints.max_height;
3342    if max_height.is_finite() {
3343        max_height = (max_height - vertical).max(0.0);
3344    }
3345    normalize_constraints(Constraints {
3346        min_width,
3347        max_width,
3348        min_height,
3349        max_height,
3350    })
3351}
3352
3353#[cfg(test)]
3354pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
3355    match alignment {
3356        HorizontalAlignment::Start => 0.0,
3357        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
3358        HorizontalAlignment::End => (available - child).max(0.0),
3359    }
3360}
3361
3362#[cfg(test)]
3363pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
3364    match alignment {
3365        VerticalAlignment::Top => 0.0,
3366        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
3367        VerticalAlignment::Bottom => (available - child).max(0.0),
3368    }
3369}
3370
3371fn resolve_dimension(
3372    base: f32,
3373    explicit: DimensionConstraint,
3374    min_override: Option<f32>,
3375    max_override: Option<f32>,
3376    min_limit: f32,
3377    max_limit: f32,
3378) -> f32 {
3379    let mut min_bound = min_limit;
3380    if let Some(min_value) = min_override {
3381        min_bound = min_bound.max(min_value);
3382    }
3383
3384    let mut max_bound = if max_limit.is_finite() {
3385        max_limit
3386    } else {
3387        max_override.unwrap_or(max_limit)
3388    };
3389    if let Some(max_value) = max_override {
3390        if max_bound.is_finite() {
3391            max_bound = max_bound.min(max_value);
3392        } else {
3393            max_bound = max_value;
3394        }
3395    }
3396    if max_bound < min_bound {
3397        max_bound = min_bound;
3398    }
3399
3400    let mut size = match explicit {
3401        DimensionConstraint::Points(points) => points,
3402        DimensionConstraint::Fraction(fraction) => {
3403            if max_limit.is_finite() {
3404                max_limit * fraction.clamp(0.0, 1.0)
3405            } else {
3406                base
3407            }
3408        }
3409        DimensionConstraint::Unspecified => base,
3410        // Intrinsic sizing is resolved at a higher level where we have access to children.
3411        // At this point we just use the base size as a fallback.
3412        DimensionConstraint::Intrinsic(_) => base,
3413    };
3414
3415    size = clamp_dimension(size, min_bound, max_bound);
3416    size = clamp_dimension(size, min_limit, max_limit);
3417    size.max(0.0)
3418}
3419
3420fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
3421    let mut result = value.max(min);
3422    if max.is_finite() {
3423        result = result.min(max);
3424    }
3425    result
3426}
3427
3428fn normalize_constraints(mut constraints: Constraints) -> Constraints {
3429    if constraints.max_width < constraints.min_width {
3430        constraints.max_width = constraints.min_width;
3431    }
3432    if constraints.max_height < constraints.min_height {
3433        constraints.max_height = constraints.min_height;
3434    }
3435    constraints
3436}
3437
3438#[cfg(test)]
3439#[path = "tests/layout_tests.rs"]
3440mod tests;