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        // Accumulated ancestor graphics-layer translation (window px). A node's
730        // drawn content is offset by every ancestor layer's translation on top
731        // of its layout position, so a text field's true on-screen origin must
732        // add this. Scroll offsets are already baked into `parent_content_origin`
733        // via placement; this carries the extra translation-transform component.
734        parent_layer_translation: Point,
735    ) -> Result<Option<LayoutBox>, NodeError> {
736        let Some((state, child_ids)) = snapshot(applier, node_id)? else {
737            return Ok(None);
738        };
739        if !state.is_placed {
740            return Ok(None);
741        }
742
743        let top_left = Point {
744            x: parent_content_origin.x + state.position.x,
745            y: parent_content_origin.y + state.position.y,
746        };
747        let rect = GeometryRect {
748            x: top_left.x,
749            y: top_left.y,
750            width: state.size.width,
751            height: state.size.height,
752        };
753        let info = runtime_metadata_for(applier, node_id)?;
754        let kind = layout_kind_from_metadata(node_id, &info);
755        let RuntimeNodeMetadata {
756            modifier,
757            resolved_modifiers,
758            modifier_slices,
759            ..
760        } = info;
761
762        // Add this node's own graphics-layer translation to the accumulated
763        // ancestor translation: it shifts where this node (and its subtree) is
764        // drawn relative to its layout box. (Scale/rotation are not folded in —
765        // handle positioning under a zoom/rotation layer is a documented gap.)
766        let layer_translation = match modifier_slices.graphics_layer() {
767            Some(layer) => Point {
768                x: parent_layer_translation.x + layer.translation_x,
769                y: parent_layer_translation.y + layer.translation_y,
770            },
771            None => parent_layer_translation,
772        };
773
774        // Publish the field's TRUE composited window origin for its selection
775        // handles: layout position (scroll already baked in) + accumulated layer
776        // translation. Re-read every layout pass so handles track live scrolling.
777        if let Some(sink) = modifier_slices.text_field_window_origin() {
778            sink.set(Point {
779                x: top_left.x + layer_translation.x,
780                y: top_left.y + layer_translation.y,
781            });
782        }
783
784        // Publish a scroll container's composited viewport rect (window
785        // coordinates) for its `BringIntoViewResponder`, so a focused
786        // descendant field can be scrolled above the soft keyboard.
787        if let Some(sink) = modifier_slices.viewport_window_rect() {
788            sink.set(GeometryRect {
789                x: top_left.x + layer_translation.x,
790                y: top_left.y + layer_translation.y,
791                width: state.size.width,
792                height: state.size.height,
793            });
794        }
795
796        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
797        let child_origin = Point {
798            x: top_left.x + state.content_offset.x,
799            y: top_left.y + state.content_offset.y,
800        };
801        let mut children = Vec::with_capacity(child_ids.len());
802        for child_id in child_ids {
803            if let Some(child) = place(applier, child_id, child_origin, layer_translation)? {
804                children.push(child);
805            }
806        }
807
808        Ok(Some(LayoutBox::new(
809            node_id,
810            rect,
811            state.content_offset,
812            data,
813            children,
814        )))
815    }
816
817    place(applier, root, Point::default(), Point::default()).map(|root| root.map(LayoutTree::new))
818}
819
820/// Builds a semantics snapshot from retained layout state in the live applier tree.
821///
822/// This is the on-demand counterpart to [`build_layout_tree_from_applier`].
823/// It follows the currently placed child set, including subcompose active
824/// children, and clears semantics dirty flags for nodes it visits.
825pub fn build_semantics_tree_from_applier(
826    applier: &mut MemoryApplier,
827    root: NodeId,
828) -> Result<Option<SemanticsTree>, NodeError> {
829    fn node(
830        applier: &mut MemoryApplier,
831        node_id: NodeId,
832    ) -> Result<Option<SemanticsNode>, NodeError> {
833        match applier.with_node::<LayoutNode, _>(node_id, |layout| {
834            let state = layout.layout_state();
835            if !state.is_placed {
836                return None;
837            }
838            let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
839            let config = layout.semantics_configuration();
840            let children = layout.children.clone();
841            layout.clear_needs_semantics();
842            Some((role, config, children))
843        }) {
844            Ok(Some((role, config, child_ids))) => {
845                let mut children = Vec::with_capacity(child_ids.len());
846                for child_id in child_ids {
847                    if let Some(child) = node(applier, child_id)? {
848                        children.push(child);
849                    }
850                }
851                return Ok(Some(semantics_node_from_parts(
852                    node_id, role, config, children,
853                )));
854            }
855            Ok(None) => return Ok(None),
856            Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {}
857            Err(err) => return Err(err),
858        }
859
860        match applier.with_node::<SubcomposeLayoutNode, _>(node_id, |subcompose| {
861            let state = subcompose.layout_state();
862            if !state.is_placed {
863                return None;
864            }
865            let config = collect_semantics_from_modifier(&subcompose.modifier());
866            let children = subcompose.active_children();
867            subcompose.clear_needs_semantics();
868            Some((config, children))
869        }) {
870            Ok(Some((config, child_ids))) => {
871                let mut children = Vec::with_capacity(child_ids.len());
872                for child_id in child_ids {
873                    if let Some(child) = node(applier, child_id)? {
874                        children.push(child);
875                    }
876                }
877                Ok(Some(semantics_node_from_parts(
878                    node_id,
879                    SemanticsRole::Subcompose,
880                    config,
881                    children,
882                )))
883            }
884            Ok(None) | Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
885                Ok(None)
886            }
887            Err(err) => Err(err),
888        }
889    }
890
891    node(applier, root).map(|root| root.map(SemanticsTree::new))
892}
893
894#[derive(Clone, Copy, Debug, PartialEq, Eq)]
895pub struct MeasureLayoutOptions {
896    pub collect_semantics: bool,
897    pub build_layout_tree: bool,
898}
899
900impl Default for MeasureLayoutOptions {
901    fn default() -> Self {
902        Self {
903            collect_semantics: true,
904            build_layout_tree: true,
905        }
906    }
907}
908
909/// Check if a node or any of its descendants needs measure (selective measure optimization).
910/// This can be used by the app shell to skip layout when the tree is clean.
911///
912/// O(1) check - just looks at root's dirty flag.
913/// Works because all mutation paths bubble dirty flags to root via composer commands.
914///
915/// Returns Result to force caller to handle errors explicitly. No more unwrap_or(true) safety net.
916pub fn tree_needs_layout(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
917    Ok(applier.get_mut(root)?.needs_layout())
918}
919
920/// Check if the root semantics snapshot is dirty.
921///
922/// Semantics invalidations bubble to the root the same way layout invalidations do,
923/// so a root check is sufficient to determine whether the next layout pass needs to
924/// rebuild semantic data even when geometry is otherwise unchanged.
925pub fn tree_needs_semantics(applier: &mut dyn Applier, root: NodeId) -> Result<bool, NodeError> {
926    Ok(applier.get_mut(root)?.needs_semantics())
927}
928
929/// Test helper: bubbles layout dirty flag to root.
930#[cfg(test)]
931pub(crate) fn bubble_layout_dirty(applier: &mut MemoryApplier, node_id: NodeId) {
932    cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
933}
934
935/// Runs the measure phase for the subtree rooted at `root`.
936pub fn measure_layout(
937    applier: &mut MemoryApplier,
938    root: NodeId,
939    max_size: Size,
940) -> Result<LayoutMeasurements, NodeError> {
941    measure_layout_with_options(applier, root, max_size, MeasureLayoutOptions::default())
942}
943
944pub fn measure_layout_with_options(
945    applier: &mut MemoryApplier,
946    root: NodeId,
947    max_size: Size,
948    options: MeasureLayoutOptions,
949) -> Result<LayoutMeasurements, NodeError> {
950    let telemetry_start = Instant::now();
951    process_pending_layout_repasses(applier, root)?;
952    let after_repasses = Instant::now();
953
954    let constraints = Constraints {
955        min_width: 0.0,
956        max_width: max_size.width,
957        min_height: 0.0,
958        max_height: max_size.height,
959    };
960
961    // Selective measure: only increment epoch if something needs MEASURING (not just layout)
962    // O(1) check - just look at root's dirty flag (bubbling ensures correctness)
963    //
964    // CRITICAL: We check needs_MEASURE, not needs_LAYOUT!
965    // - needs_measure: size may change, caches must be invalidated
966    // - needs_layout: position may change but size is cached (e.g., scroll)
967    //
968    // Scroll operations bubble needs_layout to ancestors, but NOT needs_measure.
969    // Using needs_layout here would wipe ALL caches on every scroll frame, causing
970    // O(N) full remeasurement instead of O(changed nodes).
971    let (needs_remeasure, _needs_semantics, cached_epoch) = match applier
972        .with_node::<LayoutNode, _>(root, |node| {
973            (
974                node.needs_measure(), // CORRECT: check needs_measure, not needs_layout
975                node.needs_semantics(),
976                node.cache_handles().epoch(),
977            )
978        }) {
979        Ok(tuple) => tuple,
980        Err(NodeError::TypeMismatch { .. }) => {
981            let node = applier.get_mut(root)?;
982            // Non-LayoutNode roots still expose Node dirty flags.
983            // Use needs_measure here so layout-only subtree repasses can reuse
984            // the existing cache epoch instead of invalidating the whole tree.
985            let measure_dirty = node.needs_measure();
986            let semantics_dirty = node.needs_semantics();
987            (measure_dirty, semantics_dirty, 0)
988        }
989        Err(err) => return Err(err),
990    };
991
992    let epoch = if needs_remeasure {
993        crate::render_state::next_layout_cache_epoch()
994    } else if cached_epoch != 0 {
995        cached_epoch
996    } else {
997        // Fallback when caller root isn't a LayoutNode (e.g. tests using Spacer directly).
998        crate::render_state::current_layout_cache_epoch()
999    };
1000
1001    // Move the current applier into a host and set up a guard that will
1002    // ALWAYS restore:
1003    // - the MemoryApplier back into `applier`
1004    // - the SlotTable back into that MemoryApplier
1005    //
1006    // IMPORTANT: Declare the guard *before* the builder so the builder
1007    // is dropped first (both on Ok and on unwind).
1008    let guard = ApplierSlotGuard::new(applier);
1009    let applier_host = guard.host();
1010    let slots_handle = guard.slots_handle();
1011    let after_guard = Instant::now();
1012
1013    // Give the builder the shared slots handle - both guard and builder
1014    // now share access to the same SlotTable via Rc<RefCell<_>>.
1015    let frame_arena = crate::render_state::take_layout_frame_arena();
1016    let mut builder = LayoutBuilder::new_with_epoch(
1017        Rc::clone(&applier_host),
1018        epoch,
1019        Rc::clone(&slots_handle),
1020        frame_arena,
1021    );
1022    let after_builder = Instant::now();
1023
1024    // ---- Measurement -------------------------------------------------------
1025    // If measurement fails, the guard will restore slots from the shared handle
1026    // on drop - this is safe because the handle always contains valid slots.
1027
1028    let measured = builder.measure_node(root, normalize_constraints(constraints))?;
1029    let after_measure = Instant::now();
1030
1031    // Root node has no parent to place it, so we must explicitly place it at (0,0).
1032    // This ensures is_placed=true, allowing the renderer to traverse the tree.
1033    // Handle both LayoutNode and SubcomposeLayoutNode as potential roots.
1034    if let Ok(mut applier) = applier_host.try_borrow_typed() {
1035        if applier
1036            .with_node::<LayoutNode, _>(root, |node| {
1037                node.set_position(Point::default());
1038            })
1039            .is_err()
1040        {
1041            let _ = applier.with_node::<SubcomposeLayoutNode, _>(root, |node| {
1042                node.set_position(Point::default());
1043            });
1044        }
1045    }
1046    let after_root_place = Instant::now();
1047
1048    let (layout_tree, semantics) = {
1049        let mut applier_ref = applier_host.borrow_typed();
1050        let layout_tree = if options.build_layout_tree {
1051            Some(build_layout_tree(&mut applier_ref, &measured)?)
1052        } else {
1053            None
1054        };
1055        let semantics = if options.collect_semantics {
1056            let semantics_tree = if let Some(layout_tree) = layout_tree.as_ref() {
1057                clear_semantics_dirty_flags(&mut applier_ref, &measured)?;
1058                build_semantics_tree_from_layout_tree(layout_tree)
1059            } else {
1060                build_semantics_tree_from_live_nodes(&mut applier_ref, &measured)?
1061            };
1062            Some(semantics_tree)
1063        } else {
1064            None
1065        };
1066        (layout_tree, semantics)
1067    };
1068    let after_aux = Instant::now();
1069
1070    // Drop builder before guard - slots are already in the shared handle.
1071    // Guard's Drop will write them back to the applier.
1072    drop(builder);
1073    let after_builder_drop = Instant::now();
1074
1075    // DO NOT manually unwrap `applier_host` or replace `applier` here.
1076    // `ApplierSlotGuard::drop` will restore everything when this function returns.
1077    drop(guard);
1078    let after_guard_drop = Instant::now();
1079
1080    log_layout_measure_telemetry(LayoutMeasureTelemetry {
1081        root,
1082        start: telemetry_start,
1083        after_repasses,
1084        after_guard,
1085        after_builder,
1086        after_measure,
1087        after_root_place,
1088        after_aux,
1089        after_builder_drop,
1090        after_guard_drop,
1091    });
1092
1093    Ok(LayoutMeasurements::new(measured, semantics, layout_tree))
1094}
1095
1096fn process_pending_layout_repasses(
1097    applier: &mut MemoryApplier,
1098    root: NodeId,
1099) -> Result<(), NodeError> {
1100    for node_id in crate::render_state::take_modifier_slice_repass_nodes() {
1101        if let Ok(node) = applier.get_mut(node_id) {
1102            let any = node.as_any_mut();
1103            if let Some(layout) = any.downcast_mut::<crate::widgets::nodes::LayoutNode>() {
1104                layout.mark_modifier_slices_dirty();
1105            } else if let Some(subcompose) =
1106                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1107            {
1108                subcompose.mark_modifier_slices_dirty();
1109            }
1110        }
1111    }
1112    // Measure repasses re-*size* a subtree (and its ancestors), so an enclosing
1113    // LazyColumn re-measures the node instead of reusing its cached item slot.
1114    let measure_repass_nodes = crate::take_measure_repass_nodes();
1115    let repass_nodes = crate::take_layout_repass_nodes();
1116    if measure_repass_nodes.is_empty() && repass_nodes.is_empty() {
1117        return Ok(());
1118    }
1119    for node_id in measure_repass_nodes {
1120        cranpose_core::bubble_measure_dirty(applier as &mut dyn Applier, node_id);
1121    }
1122    for node_id in repass_nodes {
1123        cranpose_core::bubble_layout_dirty(applier as &mut dyn Applier, node_id);
1124    }
1125    applier.get_mut(root)?.mark_needs_layout();
1126    Ok(())
1127}
1128
1129struct LayoutBuilder {
1130    state: Rc<RefCell<LayoutBuilderState>>,
1131}
1132
1133impl LayoutBuilder {
1134    fn new_with_epoch(
1135        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1136        epoch: u64,
1137        slots: Rc<RefCell<SlotTable>>,
1138        frame_arena: FrameLayoutArena,
1139    ) -> Self {
1140        Self {
1141            state: Rc::new(RefCell::new(LayoutBuilderState::new_with_epoch(
1142                applier,
1143                epoch,
1144                slots,
1145                frame_arena,
1146            ))),
1147        }
1148    }
1149
1150    fn measure_node(
1151        &mut self,
1152        node_id: NodeId,
1153        constraints: Constraints,
1154    ) -> Result<Rc<MeasuredNode>, NodeError> {
1155        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
1156    }
1157
1158    fn set_runtime_handle(&mut self, handle: Option<RuntimeHandle>) {
1159        self.state.borrow_mut().runtime_handle = handle;
1160    }
1161}
1162
1163impl Drop for LayoutBuilder {
1164    fn drop(&mut self) {
1165        if Rc::strong_count(&self.state) != 1 {
1166            return;
1167        }
1168        let Ok(mut state) = self.state.try_borrow_mut() else {
1169            return;
1170        };
1171        crate::render_state::replace_layout_frame_arena(std::mem::take(&mut state.frame_arena));
1172    }
1173}
1174
1175struct LayoutBuilderState {
1176    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1177    runtime_handle: Option<RuntimeHandle>,
1178    /// Shared handle to the slot table. This is shared with ApplierSlotGuard
1179    /// to ensure panic-safety: even if we panic, the guard can restore slots.
1180    slots: Rc<RefCell<SlotTable>>,
1181    cache_epoch: u64,
1182    frame_arena: FrameLayoutArena,
1183}
1184
1185struct LayoutRuntimeFrameBindingCleanup {
1186    state: Rc<RefCell<LayoutRuntimeState>>,
1187}
1188
1189impl LayoutRuntimeFrameBindingCleanup {
1190    fn new(state: Rc<RefCell<LayoutRuntimeState>>) -> Self {
1191        Self { state }
1192    }
1193}
1194
1195impl Drop for LayoutRuntimeFrameBindingCleanup {
1196    fn drop(&mut self) {
1197        self.state.borrow().clear_frame_bindings();
1198    }
1199}
1200
1201impl LayoutBuilderState {
1202    fn new_with_epoch(
1203        applier: Rc<ConcreteApplierHost<MemoryApplier>>,
1204        epoch: u64,
1205        slots: Rc<RefCell<SlotTable>>,
1206        frame_arena: FrameLayoutArena,
1207    ) -> Self {
1208        let runtime_handle = applier.borrow_typed().runtime_handle();
1209
1210        Self {
1211            applier,
1212            runtime_handle,
1213            slots,
1214            cache_epoch: epoch,
1215            frame_arena,
1216        }
1217    }
1218
1219    fn try_with_applier_result<R>(
1220        state_rc: &Rc<RefCell<Self>>,
1221        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
1222    ) -> Option<Result<R, NodeError>> {
1223        let host = {
1224            let state = state_rc.borrow();
1225            Rc::clone(&state.applier)
1226        };
1227
1228        // Try to borrow - if already borrowed (nested call), return None
1229        let Ok(mut applier) = host.try_borrow_typed() else {
1230            return None;
1231        };
1232
1233        Some(f(&mut applier))
1234    }
1235
1236    fn with_applier_result<R>(
1237        state_rc: &Rc<RefCell<Self>>,
1238        f: impl FnOnce(&mut MemoryApplier) -> Result<R, NodeError>,
1239    ) -> Result<R, NodeError> {
1240        Self::try_with_applier_result(state_rc, f).unwrap_or_else(|| {
1241            Err(NodeError::MissingContext {
1242                id: NodeId::default(),
1243                reason: "applier already borrowed",
1244            })
1245        })
1246    }
1247
1248    /// Clears the is_placed flag for a node at the start of measurement.
1249    /// This ensures nodes that drop out of placement won't render with stale geometry.
1250    fn clear_node_placed(state_rc: &Rc<RefCell<Self>>, node_id: NodeId) {
1251        let host = {
1252            let state = state_rc.borrow();
1253            Rc::clone(&state.applier)
1254        };
1255        let Ok(mut applier) = host.try_borrow_typed() else {
1256            return;
1257        };
1258        // Try LayoutNode first, then SubcomposeLayoutNode
1259        if applier
1260            .with_node::<LayoutNode, _>(node_id, |node| {
1261                node.clear_placed();
1262            })
1263            .is_err()
1264        {
1265            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1266                node.clear_placed();
1267            });
1268        }
1269    }
1270
1271    fn measure_node(
1272        state_rc: Rc<RefCell<Self>>,
1273        node_id: NodeId,
1274        constraints: Constraints,
1275    ) -> Result<Rc<MeasuredNode>, NodeError> {
1276        let telemetry_start = Instant::now();
1277        // Clear is_placed at the start of measurement.
1278        // Nodes that are placed will have is_placed set to true via Placeable::place().
1279        // Nodes that drop out of placement (not placed this pass) will remain is_placed=false.
1280        Self::clear_node_placed(&state_rc, node_id);
1281
1282        // Try SubcomposeLayoutNode first
1283        if let Some(subcompose) =
1284            Self::try_measure_subcompose(Rc::clone(&state_rc), node_id, constraints)?
1285        {
1286            log_node_measure_telemetry(
1287                "subcompose",
1288                node_id,
1289                constraints,
1290                subcompose.size,
1291                subcompose.children.len(),
1292                telemetry_start,
1293            );
1294            return Ok(subcompose);
1295        }
1296
1297        // Try LayoutNode (the primary modern path)
1298        if let Some(result) = Self::try_with_applier_result(&state_rc, |applier| {
1299            match applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1300                LayoutNodeSnapshot::from_layout_node(layout_node)
1301            }) {
1302                Ok(snapshot) => Ok(Some(snapshot)),
1303                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => Ok(None),
1304                Err(err) => Err(err),
1305            }
1306        }) {
1307            // Applier was available, process the result
1308            if let Some(snapshot) = result? {
1309                let measured = Self::measure_layout_node(
1310                    Rc::clone(&state_rc),
1311                    node_id,
1312                    snapshot,
1313                    constraints,
1314                )?;
1315                log_node_measure_telemetry(
1316                    "layout",
1317                    node_id,
1318                    constraints,
1319                    measured.size,
1320                    measured.children.len(),
1321                    telemetry_start,
1322                );
1323                return Ok(measured);
1324            }
1325        }
1326        // If applier was busy (None) or snapshot was None, fall through to fallback
1327
1328        // No alternate fallbacks - all widgets use LayoutNode or SubcomposeLayoutNode
1329        // If we reach here, it's an unknown node type (shouldn't happen in normal use)
1330        let measured = Rc::new(MeasuredNode::new(
1331            node_id,
1332            Size::default(),
1333            Point { x: 0.0, y: 0.0 },
1334            Point::default(), // No content offset for fallback nodes
1335            Vec::new(),
1336        ));
1337        log_node_measure_telemetry(
1338            "fallback",
1339            node_id,
1340            constraints,
1341            measured.size,
1342            measured.children.len(),
1343            telemetry_start,
1344        );
1345        Ok(measured)
1346    }
1347
1348    fn cached_measure_node_with_applier(
1349        applier: &mut MemoryApplier,
1350        node_id: NodeId,
1351        constraints: Constraints,
1352    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1353        let Some(data) = Self::layout_child_measure_data(applier, node_id)? else {
1354            return Ok(None);
1355        };
1356        if data.needs_measure || data.cache.epoch() == 0 {
1357            return Ok(None);
1358        }
1359
1360        let Some(measured) = data.cache.get_measurement(constraints) else {
1361            return Ok(None);
1362        };
1363
1364        if let Some(layout_state) = data.layout_state {
1365            let mut layout_state = layout_state.borrow_mut();
1366            layout_state.size = measured.size;
1367            layout_state.measurement_constraints = constraints;
1368            drop(layout_state);
1369            let _ = applier.with_node::<LayoutNode, _>(node_id, |node| {
1370                if data.needs_layout {
1371                    node.clear_needs_layout();
1372                }
1373            });
1374        } else {
1375            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
1376                node.set_measured_size(measured.size);
1377                if data.needs_layout {
1378                    node.clear_needs_layout();
1379                }
1380            });
1381        }
1382
1383        Ok(Some(measured))
1384    }
1385
1386    fn try_measure_subcompose(
1387        state_rc: Rc<RefCell<Self>>,
1388        node_id: NodeId,
1389        constraints: Constraints,
1390    ) -> Result<Option<Rc<MeasuredNode>>, NodeError> {
1391        let applier_host = {
1392            let state = state_rc.borrow();
1393            Rc::clone(&state.applier)
1394        };
1395
1396        let (node_handle, resolved_modifiers) = {
1397            // Try to borrow - if already borrowed (nested measurement), return None
1398            let Ok(mut applier) = applier_host.try_borrow_typed() else {
1399                return Ok(None);
1400            };
1401            let node = match applier.get_mut(node_id) {
1402                Ok(node) => node,
1403                Err(NodeError::Missing { .. }) => return Ok(None),
1404                Err(err) => return Err(err),
1405            };
1406            let any = node.as_any_mut();
1407            if let Some(subcompose) =
1408                any.downcast_mut::<crate::subcompose_layout::SubcomposeLayoutNode>()
1409            {
1410                let handle = subcompose.handle();
1411                let resolved_modifiers = handle.resolved_modifiers();
1412                (handle, resolved_modifiers)
1413            } else {
1414                return Ok(None);
1415            }
1416        };
1417
1418        let runtime_handle = {
1419            let mut state = state_rc.borrow_mut();
1420            if state.runtime_handle.is_none() {
1421                // Try to borrow - if already borrowed, we can't get runtime handle
1422                if let Ok(applier) = applier_host.try_borrow_typed() {
1423                    state.runtime_handle = applier.runtime_handle();
1424                }
1425            }
1426            state
1427                .runtime_handle
1428                .clone()
1429                .ok_or(NodeError::MissingContext {
1430                    id: node_id,
1431                    reason: "runtime handle required for subcomposition",
1432                })?
1433        };
1434
1435        let props = resolved_modifiers.layout_properties();
1436        let padding = resolved_modifiers.padding();
1437        let offset = resolved_modifiers.offset();
1438        let mut inner_constraints = normalize_constraints(subtract_padding(constraints, padding));
1439
1440        if let DimensionConstraint::Points(width) = props.width() {
1441            let constrained_width = width - padding.horizontal_sum();
1442            inner_constraints.max_width = inner_constraints.max_width.min(constrained_width);
1443            inner_constraints.min_width = inner_constraints.min_width.min(constrained_width);
1444        }
1445        if let DimensionConstraint::Points(height) = props.height() {
1446            let constrained_height = height - padding.vertical_sum();
1447            inner_constraints.max_height = inner_constraints.max_height.min(constrained_height);
1448            inner_constraints.min_height = inner_constraints.min_height.min(constrained_height);
1449        }
1450
1451        let mut slots_guard = SlotsGuard::take(Rc::clone(&state_rc));
1452        let slots_host = slots_guard.host();
1453        let applier_host_dyn: Rc<dyn ApplierHost> = applier_host.clone();
1454        let observer = SnapshotStateObserver::new(|callback| callback());
1455        let composer = Composer::new(
1456            Rc::clone(&slots_host),
1457            applier_host_dyn,
1458            runtime_handle.clone(),
1459            observer,
1460            Some(node_id),
1461        );
1462        composer.enter_phase(Phase::Measure);
1463
1464        let state_rc_clone = Rc::clone(&state_rc);
1465        let measure_error = RefCell::new(None);
1466        let state_rc_for_subcompose = Rc::clone(&state_rc_clone);
1467        let error_for_subcompose = &measure_error;
1468        let measured_children = node_handle.measured_children_scratch();
1469        let measured_children_for_subcompose = Rc::clone(&measured_children);
1470        let state_rc_for_cached = Rc::clone(&state_rc_clone);
1471        let error_for_cached = &measure_error;
1472        let measured_children_for_cached = Rc::clone(&measured_children);
1473        let measured_children_for_lookup = Rc::clone(&measured_children);
1474        let measured_children_for_retained = Rc::clone(&measured_children);
1475
1476        let measure_result = node_handle.measure_with_cached_batch(
1477            &composer,
1478            node_id,
1479            inner_constraints,
1480            CachedBatchMeasureInputs {
1481                measurer: Box::new(
1482                    move |child_id: NodeId, child_constraints: Constraints| -> Size {
1483                        match Self::measure_node(
1484                            Rc::clone(&state_rc_for_subcompose),
1485                            child_id,
1486                            child_constraints,
1487                        ) {
1488                            Ok(measured) => {
1489                                measured_children_for_subcompose
1490                                    .borrow_mut()
1491                                    .insert(child_id, Rc::clone(&measured));
1492                                measured.size
1493                            }
1494                            Err(err) => {
1495                                let mut slot = error_for_subcompose.borrow_mut();
1496                                if slot.is_none() {
1497                                    *slot = Some(err);
1498                                }
1499                                Size::default()
1500                            }
1501                        }
1502                    },
1503                ),
1504                cached_measure_batch_registrar: Box::new(
1505                    move |child_ids: &[NodeId],
1506                          child_constraints: Constraints,
1507                          out: &mut Vec<Option<Size>>| {
1508                        out.clear();
1509                        out.resize(child_ids.len(), None);
1510
1511                        let applier_host = {
1512                            let state = state_rc_for_cached.borrow();
1513                            Rc::clone(&state.applier)
1514                        };
1515                        let Ok(mut applier) = applier_host.try_borrow_typed() else {
1516                            return;
1517                        };
1518
1519                        let mut measured_children = measured_children_for_cached.borrow_mut();
1520                        for (index, &child_id) in child_ids.iter().enumerate() {
1521                            match Self::cached_measure_node_with_applier(
1522                                &mut applier,
1523                                child_id,
1524                                child_constraints,
1525                            ) {
1526                                Ok(Some(measured)) => {
1527                                    out[index] = Some(measured.size);
1528                                    measured_children.insert(child_id, Rc::clone(&measured));
1529                                }
1530                                Ok(None) => {}
1531                                Err(err) => {
1532                                    let mut slot = error_for_cached.borrow_mut();
1533                                    if slot.is_none() {
1534                                        *slot = Some(err);
1535                                    }
1536                                    break;
1537                                }
1538                            }
1539                        }
1540                    },
1541                ),
1542                retained_measure_lookup: Box::new(move |child_id| {
1543                    measured_children_for_lookup
1544                        .borrow()
1545                        .get(&child_id)
1546                        .cloned()
1547                }),
1548                retained_measure_registrar: Box::new(move |measurements| {
1549                    let mut measured_children = measured_children_for_retained.borrow_mut();
1550                    for measured in measurements {
1551                        measured_children.insert(measured.node_id(), Rc::clone(measured));
1552                    }
1553                }),
1554                error: &measure_error,
1555            },
1556        )?;
1557        drop(composer);
1558        slots_guard.restore(slots_host.into_table()?);
1559
1560        if let Some(err) = measure_error.borrow_mut().take() {
1561            return Err(err);
1562        }
1563
1564        // NOTE: Children are now managed by the composer via insert_child commands
1565        // (from parent_stack initialization with root). set_active_children is no longer used.
1566
1567        let cranpose_ui_layout::MeasureResult {
1568            size: measured_size,
1569            placements,
1570        } = measure_result;
1571
1572        let mut width = measured_size.width + padding.horizontal_sum();
1573        let mut height = measured_size.height + padding.vertical_sum();
1574
1575        width = resolve_dimension(
1576            width,
1577            props.width(),
1578            props.min_width(),
1579            props.max_width(),
1580            constraints.min_width,
1581            constraints.max_width,
1582        );
1583        height = resolve_dimension(
1584            height,
1585            props.height(),
1586            props.min_height(),
1587            props.max_height(),
1588            constraints.min_height,
1589            constraints.max_height,
1590        );
1591
1592        let mut children = Vec::with_capacity(placements.len());
1593        let mut measured_children_by_id = measured_children.borrow_mut();
1594
1595        // Update the SubcomposeLayoutNode's size (position will be set by parent's placement)
1596        if let Ok(mut applier) = applier_host.try_borrow_typed() {
1597            let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |parent_node| {
1598                parent_node.set_measured_size(Size { width, height });
1599                parent_node.clear_needs_measure();
1600                parent_node.clear_needs_layout();
1601            });
1602        }
1603
1604        for placement in &placements {
1605            let child = if let Some(measured) = measured_children_by_id.remove(&placement.node_id) {
1606                measured
1607            } else {
1608                // Policies may place subcomposed children without calling `measure()` first
1609                // (for example, when they only need a slot's rendered content). Keep the
1610                // existing fallback for that case, but preserve the policy-time measurement
1611                // whenever it exists so we don't silently remeasure lazy items with the
1612                // container's tighter constraints.
1613                Self::measure_node(Rc::clone(&state_rc), placement.node_id, inner_constraints)?
1614            };
1615            let policy_position = Point {
1616                x: padding.left + placement.x,
1617                y: padding.top + placement.y,
1618            };
1619            // Subcompose policies return raw placements and bypass the
1620            // child's `Placeable::place`, which normally adds the modifier
1621            // chain's node offset. Retained rendering reads the live node
1622            // position, so it must receive that same final coordinate. The
1623            // measured-tree edge keeps the raw policy position because
1624            // `build_layout_tree` applies `child.offset` itself.
1625            let retained_position = Point {
1626                x: policy_position.x + child.offset.x,
1627                y: policy_position.y + child.offset.y,
1628            };
1629
1630            // Critical: Update the child's retained placement state.
1631            // Standard layouts do this via Placeable::place(), but SubcomposeLayout
1632            // logic bypasses Placeables and returns raw Placements. A subcomposed
1633            // child can itself be a SubcomposeLayout (e.g. a `BoxWithConstraints`
1634            // inside a `LazyColumn` item), so both node kinds must be positioned
1635            // and marked placed; otherwise the applier-traversal render, layout,
1636            // and semantics builds cull the child's whole subtree (issue #305).
1637            if let Ok(mut applier) = applier_host.try_borrow_typed() {
1638                if applier
1639                    .with_node::<LayoutNode, _>(placement.node_id, |node| {
1640                        node.set_position(retained_position);
1641                    })
1642                    .is_err()
1643                {
1644                    let _ =
1645                        applier.with_node::<SubcomposeLayoutNode, _>(placement.node_id, |node| {
1646                            node.set_position(retained_position);
1647                        });
1648                }
1649            }
1650
1651            children.push(MeasuredChild {
1652                node: child,
1653                offset: policy_position,
1654            });
1655        }
1656
1657        // Update the SubcomposeLayoutNode's active children for rendering
1658        node_handle.set_active_children(children.iter().map(|c| c.node.node_id));
1659        node_handle.recycle_placement_scratch(placements);
1660
1661        Ok(Some(Rc::new(MeasuredNode::new(
1662            node_id,
1663            Size { width, height },
1664            offset,
1665            Point::default(), // Subcompose nodes: content_offset handled by child layout
1666            children,
1667        ))))
1668    }
1669    /// Measures through the layout modifier coordinator chain using reconciled modifier nodes.
1670    /// Iterates through LayoutModifierNode instances from the ModifierNodeChain and calls
1671    /// their measure() methods through the retained coordinator chain.
1672    ///
1673    /// Always succeeds, measuring either directly or through retained layout modifier nodes.
1674    ///
1675    fn measure_through_modifier_chain(
1676        state_rc: &Rc<RefCell<Self>>,
1677        node_id: NodeId,
1678        runtime_state: &mut LayoutRuntimeState,
1679        measure_policy: &Rc<dyn MeasurePolicy>,
1680        constraints: Constraints,
1681        layout_node_data: &mut Vec<LayoutModifierNodeData>,
1682        placements: &mut Vec<Placement>,
1683    ) -> ModifierChainMeasurement {
1684        use cranpose_foundation::NodeCapabilities;
1685
1686        // Collect layout node information from the modifier chain
1687        layout_node_data.clear();
1688        let mut offset = Point::default();
1689
1690        {
1691            let state = state_rc.borrow();
1692            let mut applier = state.applier.borrow_typed();
1693
1694            let _ = applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1695                let chain_handle = layout_node.modifier_chain();
1696
1697                if !chain_handle.has_layout_nodes() {
1698                    return;
1699                }
1700
1701                // Collect indices and node Rc clones for layout modifier nodes
1702                chain_handle.chain().for_each_forward_matching(
1703                    NodeCapabilities::LAYOUT,
1704                    |node_ref| {
1705                        if let Some(index) = node_ref.entry_index() {
1706                            // Get the Rc clone for this node
1707                            if let Some(node_rc) = chain_handle.chain().get_node_rc(index) {
1708                                layout_node_data.push((index, Rc::clone(&node_rc)));
1709                            }
1710
1711                            // Extract offset from OffsetNode for the node's own position
1712                            // The coordinator chain handles placement_offset (for children),
1713                            // but the node's offset affects where IT is positioned in the parent
1714                            node_ref.with_node(|node| {
1715                                if let Some(offset_node) =
1716                                    node.as_any()
1717                                        .downcast_ref::<crate::modifier_nodes::OffsetNode>()
1718                                {
1719                                    let delta = offset_node.offset();
1720                                    offset.x += delta.x;
1721                                    offset.y += delta.y;
1722                                }
1723                            });
1724                        }
1725                    },
1726                );
1727            });
1728        }
1729
1730        // Fast path: if there are no layout modifiers, measure directly without the
1731        // retained coordinator chain frame.
1732        if layout_node_data.is_empty() {
1733            let final_size = measure_policy.measure_into(
1734                runtime_state.child_measurables(),
1735                constraints,
1736                placements,
1737            );
1738
1739            return ModifierChainMeasurement {
1740                size: final_size,
1741                content_offset: Point::default(),
1742                offset,
1743            };
1744        }
1745
1746        runtime_state.reconcile_coordinator_chain(layout_node_data.as_slice());
1747        let frame = CoordinatorFrame::new(
1748            measure_policy,
1749            runtime_state.child_measurables(),
1750            placements,
1751        );
1752
1753        // Measure through the complete coordinator chain
1754        let placeable = runtime_state
1755            .coordinator_chain()
1756            .measure_from(0, &frame, constraints);
1757        let final_size = Size {
1758            width: placeable.width(),
1759            height: placeable.height(),
1760        };
1761
1762        // Get accumulated content offset from the placeable (computed during measure)
1763        let content_offset = placeable.content_offset();
1764        let all_placement_offset = Point {
1765            x: content_offset.0,
1766            y: content_offset.1,
1767        };
1768
1769        // The content_offset for scroll/inner transforms is the accumulated placement offset
1770        // MINUS the node's own offset (which affects its position in the parent, not content position).
1771        // This properly separates: node position (offset) vs inner content position (content_offset).
1772        let content_offset = Point {
1773            x: all_placement_offset.x - offset.x,
1774            y: all_placement_offset.y - offset.y,
1775        };
1776
1777        // offset was already extracted from OffsetNode above
1778
1779        // Process any invalidations requested during measurement
1780        let invalidations = frame.take_invalidations();
1781        if !invalidations.is_empty() {
1782            // Mark the LayoutNode as needing the appropriate passes
1783            Self::with_applier_result(state_rc, |applier| {
1784                applier.with_node::<LayoutNode, _>(node_id, |layout_node| {
1785                    for kind in invalidations {
1786                        match kind {
1787                            InvalidationKind::Layout => layout_node.mark_needs_measure(),
1788                            InvalidationKind::Draw => layout_node.mark_needs_redraw(),
1789                            InvalidationKind::Semantics => layout_node.mark_needs_semantics(),
1790                            InvalidationKind::PointerInput => layout_node.mark_needs_pointer_pass(),
1791                            InvalidationKind::Focus => layout_node.mark_needs_focus_sync(),
1792                        }
1793                    }
1794                })
1795            })
1796            .ok();
1797        }
1798
1799        ModifierChainMeasurement {
1800            size: final_size,
1801            content_offset,
1802            offset,
1803        }
1804    }
1805
1806    fn layout_child_measure_data(
1807        applier: &mut MemoryApplier,
1808        child_id: NodeId,
1809    ) -> Result<Option<LayoutChildMeasureData>, NodeError> {
1810        match applier.with_node::<LayoutNode, _>(child_id, |n| LayoutChildMeasureData {
1811            cache: n.cache_handles(),
1812            layout_state: Some(n.layout_state_handle()),
1813            needs_layout: n.needs_layout(),
1814            needs_measure: n.needs_measure(),
1815        }) {
1816            Ok(data) => Ok(Some(data)),
1817            Err(NodeError::TypeMismatch { .. }) => {
1818                match applier.with_node::<SubcomposeLayoutNode, _>(child_id, |n| {
1819                    LayoutChildMeasureData {
1820                        cache: n.cache_handles(),
1821                        layout_state: None,
1822                        needs_layout: n.needs_layout(),
1823                        needs_measure: n.needs_measure(),
1824                    }
1825                }) {
1826                    Ok(data) => Ok(Some(data)),
1827                    Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
1828                        Ok(None)
1829                    }
1830                    Err(err) => Err(err),
1831                }
1832            }
1833            Err(NodeError::Missing { .. }) => Ok(None),
1834            Err(err) => Err(err),
1835        }
1836    }
1837
1838    fn measure_layout_node(
1839        state_rc: Rc<RefCell<Self>>,
1840        node_id: NodeId,
1841        snapshot: LayoutNodeSnapshot,
1842        constraints: Constraints,
1843    ) -> Result<Rc<MeasuredNode>, NodeError> {
1844        let cache_epoch = {
1845            let state = state_rc.borrow();
1846            state.cache_epoch
1847        };
1848        let LayoutNodeSnapshot {
1849            measure_policy,
1850            cache,
1851            layout_runtime_state,
1852            needs_layout,
1853            needs_measure,
1854        } = snapshot;
1855        cache.activate(cache_epoch);
1856
1857        if needs_measure {
1858            // Node has needs_measure=true
1859        }
1860
1861        // Only check cache when the node is fully clean.
1862        // needs_layout=true means either the node itself or one of its descendants
1863        // must be revisited even if the node's own measured size can stay cached.
1864        if !needs_measure && !needs_layout {
1865            // Check cache for current constraints
1866            if let Some(cached) = cache.get_measurement(constraints) {
1867                // Clear dirty flag after successful cache hit
1868                Self::with_applier_result(&state_rc, |applier| {
1869                    applier.with_node::<LayoutNode, _>(node_id, |node| {
1870                        node.clear_needs_measure();
1871                        node.clear_needs_layout();
1872                    })
1873                })
1874                .ok();
1875                return Ok(cached);
1876            }
1877        }
1878
1879        let (runtime_handle, applier_host) = {
1880            let state = state_rc.borrow();
1881            (state.runtime_handle.clone(), Rc::clone(&state.applier))
1882        };
1883
1884        let measure_handle = LayoutMeasureHandle::new(Rc::clone(&state_rc));
1885        let error = Rc::new(RefCell::new(None));
1886        let mut pools = VecPools::acquire(Rc::clone(&state_rc));
1887        let (records, child_ids, layout_node_data, placements) = pools.parts();
1888
1889        applier_host
1890            .borrow_typed()
1891            .with_node::<LayoutNode, _>(node_id, |node| {
1892                child_ids.extend_from_slice(&node.children);
1893            })?;
1894
1895        let mut valid_child_count = 0;
1896        for index in 0..child_ids.len() {
1897            let child_id = child_ids[index];
1898            let child_exists = {
1899                let mut applier = applier_host.borrow_typed();
1900                Self::layout_child_measure_data(&mut applier, child_id)?.is_some()
1901            };
1902            if child_exists {
1903                child_ids[valid_child_count] = child_id;
1904                valid_child_count += 1;
1905            }
1906        }
1907        child_ids.truncate(valid_child_count);
1908
1909        let _frame_binding_cleanup =
1910            LayoutRuntimeFrameBindingCleanup::new(Rc::clone(&layout_runtime_state));
1911
1912        {
1913            let mut runtime_state = layout_runtime_state.borrow_mut();
1914            runtime_state.reconcile_child_measurables(child_ids.as_slice());
1915
1916            for (index, &child_id) in child_ids.iter().enumerate() {
1917                let data = {
1918                    let mut applier = applier_host.borrow_typed();
1919                    Self::layout_child_measure_data(&mut applier, child_id)?
1920                };
1921                let Some(data) = data else {
1922                    continue;
1923                };
1924
1925                let child_is_dirty = data.needs_layout || data.needs_measure;
1926                let child_cache_epoch = if child_is_dirty {
1927                    cache_epoch
1928                } else {
1929                    data.cache.epoch()
1930                };
1931                let child_state = runtime_state.child_state(index);
1932                child_state.configure(LayoutChildMeasureConfig {
1933                    applier: Rc::clone(&applier_host),
1934                    node_id: child_id,
1935                    error: Rc::clone(&error),
1936                    runtime_handle: runtime_handle.clone(),
1937                    cache: data.cache,
1938                    cache_epoch: child_cache_epoch,
1939                    force_remeasure: child_is_dirty,
1940                    measure_handle: Some(measure_handle.clone()),
1941                    layout_state: data.layout_state,
1942                });
1943                records.push((child_id, ChildRecord { state: child_state }));
1944            }
1945        }
1946
1947        let chain_constraints = constraints;
1948
1949        let modifier_chain_result = {
1950            let mut runtime_state = layout_runtime_state.borrow_mut();
1951            Self::measure_through_modifier_chain(
1952                &state_rc,
1953                node_id,
1954                &mut runtime_state,
1955                &measure_policy,
1956                chain_constraints,
1957                layout_node_data,
1958                placements,
1959            )
1960        };
1961
1962        // Modifier chain always succeeds - use the node-driven measurement.
1963        let (width, height, content_offset, offset) = {
1964            let result = modifier_chain_result;
1965            // The size is already correct from the modifier chain (modifiers like SizeNode
1966            // have already enforced their constraints), so we use it directly.
1967            if let Some(err) = error.borrow_mut().take() {
1968                return Err(err);
1969            }
1970
1971            (
1972                result.size.width,
1973                result.size.height,
1974                result.content_offset,
1975                result.offset,
1976            )
1977        };
1978
1979        let mut measured_children = Vec::with_capacity(records.len());
1980        for (child_id, record) in records.iter() {
1981            if let Some(measured) = record.state.take_measured() {
1982                let base_position = placements
1983                    .iter()
1984                    .find(|placement| placement.node_id == *child_id)
1985                    .map(|placement| Point {
1986                        x: placement.x,
1987                        y: placement.y,
1988                    })
1989                    .or_else(|| record.state.last_position())
1990                    .unwrap_or(Point { x: 0.0, y: 0.0 });
1991                // Apply content_offset (from scroll/transforms) to child positioning
1992                let position = Point {
1993                    x: content_offset.x + base_position.x,
1994                    y: content_offset.y + base_position.y,
1995                };
1996                measured_children.push(MeasuredChild {
1997                    node: measured,
1998                    offset: position,
1999                });
2000            }
2001        }
2002
2003        let measured = Rc::new(MeasuredNode::new(
2004            node_id,
2005            Size { width, height },
2006            offset,
2007            content_offset,
2008            measured_children,
2009        ));
2010
2011        cache.store_measurement(constraints, Rc::clone(&measured));
2012
2013        // Clear dirty flags and update derived state
2014        Self::with_applier_result(&state_rc, |applier| {
2015            applier.with_node::<LayoutNode, _>(node_id, |node| {
2016                node.clear_needs_measure();
2017                node.clear_needs_layout();
2018                node.set_measured_size(Size { width, height });
2019                node.set_content_offset(content_offset);
2020            })
2021        })
2022        .ok();
2023
2024        Ok(measured)
2025    }
2026}
2027
2028struct LayoutChildMeasureData {
2029    cache: LayoutNodeCacheHandles,
2030    layout_state: Option<Rc<RefCell<LayoutState>>>,
2031    needs_layout: bool,
2032    needs_measure: bool,
2033}
2034
2035/// Snapshot of a LayoutNode's data for measuring.
2036/// This is a temporary copy used during the measure phase, not a live node.
2037///
2038/// Note: We capture `needs_measure` here because it's checked during measure to enable
2039/// selective measure optimization at the individual node level. Even if the tree is partially
2040/// dirty (some nodes changed), clean nodes can skip measure and use cached results.
2041struct LayoutNodeSnapshot {
2042    measure_policy: Rc<dyn MeasurePolicy>,
2043    cache: LayoutNodeCacheHandles,
2044    layout_runtime_state: Rc<RefCell<LayoutRuntimeState>>,
2045    needs_layout: bool,
2046    /// Whether this specific node needs to be measured (vs using cached measurement)
2047    needs_measure: bool,
2048}
2049
2050impl LayoutNodeSnapshot {
2051    fn from_layout_node(node: &LayoutNode) -> Self {
2052        Self {
2053            measure_policy: Rc::clone(&node.measure_policy),
2054            cache: node.cache_handles(),
2055            layout_runtime_state: node.layout_runtime_state_handle(),
2056            needs_layout: node.needs_layout(),
2057            needs_measure: node.needs_measure(),
2058        }
2059    }
2060}
2061
2062// Helper types for accessing subsets of LayoutBuilderState
2063struct VecPools {
2064    state: Rc<RefCell<LayoutBuilderState>>,
2065    records: Vec<(NodeId, ChildRecord)>,
2066    child_ids: Vec<NodeId>,
2067    layout_node_data: Vec<LayoutModifierNodeData>,
2068    placements: Vec<Placement>,
2069}
2070
2071impl VecPools {
2072    fn acquire(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2073        let (records, child_ids, layout_node_data, placements) = {
2074            let mut state_mut = state.borrow_mut();
2075            (
2076                state_mut.frame_arena.tmp_records.acquire(),
2077                state_mut.frame_arena.tmp_child_ids.acquire(),
2078                state_mut.frame_arena.tmp_layout_node_data.acquire(),
2079                state_mut.frame_arena.tmp_placements.acquire(),
2080            )
2081        };
2082        Self {
2083            state,
2084            records,
2085            child_ids,
2086            layout_node_data,
2087            placements,
2088        }
2089    }
2090
2091    #[allow(clippy::type_complexity)] // Returns internal Vec references for layout operations
2092    fn parts(
2093        &mut self,
2094    ) -> (
2095        &mut Vec<(NodeId, ChildRecord)>,
2096        &mut Vec<NodeId>,
2097        &mut Vec<LayoutModifierNodeData>,
2098        &mut Vec<Placement>,
2099    ) {
2100        (
2101            &mut self.records,
2102            &mut self.child_ids,
2103            &mut self.layout_node_data,
2104            &mut self.placements,
2105        )
2106    }
2107}
2108
2109impl Drop for VecPools {
2110    fn drop(&mut self) {
2111        let mut state = self.state.borrow_mut();
2112        state
2113            .frame_arena
2114            .tmp_records
2115            .release(std::mem::take(&mut self.records));
2116        state
2117            .frame_arena
2118            .tmp_child_ids
2119            .release(std::mem::take(&mut self.child_ids));
2120        state
2121            .frame_arena
2122            .tmp_layout_node_data
2123            .release(std::mem::take(&mut self.layout_node_data));
2124        state
2125            .frame_arena
2126            .tmp_placements
2127            .release(std::mem::take(&mut self.placements));
2128    }
2129}
2130
2131struct SlotsGuard {
2132    state: Rc<RefCell<LayoutBuilderState>>,
2133    slots: Option<SlotTable>,
2134}
2135
2136impl SlotsGuard {
2137    fn take(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2138        let slots = {
2139            let state_ref = state.borrow();
2140            let mut slots_ref = state_ref.slots.borrow_mut();
2141            std::mem::take(&mut *slots_ref)
2142        };
2143        Self {
2144            state,
2145            slots: Some(slots),
2146        }
2147    }
2148
2149    fn host(&mut self) -> Rc<SlotsHost> {
2150        let slots = self.slots.take().unwrap_or_default();
2151        Rc::new(SlotsHost::new(slots))
2152    }
2153
2154    fn restore(&mut self, slots: SlotTable) {
2155        debug_assert!(self.slots.is_none());
2156        self.slots = Some(slots);
2157    }
2158}
2159
2160impl Drop for SlotsGuard {
2161    fn drop(&mut self) {
2162        if let Some(slots) = self.slots.take() {
2163            let state_ref = self.state.borrow();
2164            *state_ref.slots.borrow_mut() = slots;
2165        }
2166    }
2167}
2168
2169#[derive(Clone)]
2170struct LayoutMeasureHandle {
2171    state: Rc<RefCell<LayoutBuilderState>>,
2172}
2173
2174impl LayoutMeasureHandle {
2175    fn new(state: Rc<RefCell<LayoutBuilderState>>) -> Self {
2176        Self { state }
2177    }
2178
2179    fn measure(
2180        &self,
2181        node_id: NodeId,
2182        constraints: Constraints,
2183    ) -> Result<Rc<MeasuredNode>, NodeError> {
2184        LayoutBuilderState::measure_node(Rc::clone(&self.state), node_id, constraints)
2185    }
2186}
2187
2188#[derive(Debug, Clone)]
2189pub(crate) struct MeasuredNode {
2190    node_id: NodeId,
2191    size: Size,
2192    /// Node's position offset relative to parent (from OffsetNode etc.)
2193    offset: Point,
2194    /// Content offset for scroll/inner transforms (NOT node position)
2195    content_offset: Point,
2196    children: Vec<MeasuredChild>,
2197}
2198
2199impl MeasuredNode {
2200    fn new(
2201        node_id: NodeId,
2202        size: Size,
2203        offset: Point,
2204        content_offset: Point,
2205        children: Vec<MeasuredChild>,
2206    ) -> Self {
2207        Self {
2208            node_id,
2209            size,
2210            offset,
2211            content_offset,
2212            children,
2213        }
2214    }
2215
2216    #[cfg(test)]
2217    pub(crate) fn leaf(node_id: NodeId, size: Size) -> Self {
2218        Self::new(
2219            node_id,
2220            size,
2221            Point::default(),
2222            Point::default(),
2223            Vec::new(),
2224        )
2225    }
2226
2227    pub(crate) fn node_id(&self) -> NodeId {
2228        self.node_id
2229    }
2230
2231    pub(crate) fn size(&self) -> Size {
2232        self.size
2233    }
2234}
2235
2236#[derive(Debug, Clone)]
2237struct MeasuredChild {
2238    node: Rc<MeasuredNode>,
2239    offset: Point,
2240}
2241
2242struct ChildRecord {
2243    state: Rc<LayoutChildMeasureState>,
2244}
2245
2246struct CoordinatorFrame<'a> {
2247    measure_policy: &'a Rc<dyn MeasurePolicy>,
2248    measurables: &'a [Box<dyn Measurable>],
2249    placements: RefCell<&'a mut Vec<Placement>>,
2250    context: RefCell<LayoutNodeContext>,
2251}
2252
2253impl<'a> CoordinatorFrame<'a> {
2254    fn new(
2255        measure_policy: &'a Rc<dyn MeasurePolicy>,
2256        measurables: &'a [Box<dyn Measurable>],
2257        placements: &'a mut Vec<Placement>,
2258    ) -> Self {
2259        Self {
2260            measure_policy,
2261            measurables,
2262            placements: RefCell::new(placements),
2263            context: RefCell::new(LayoutNodeContext::new()),
2264        }
2265    }
2266
2267    fn take_invalidations(&self) -> Vec<InvalidationKind> {
2268        self.context.borrow_mut().take_invalidations()
2269    }
2270}
2271
2272struct CoordinatorLink<'chain, 'frame_ref, 'frame_data> {
2273    chain: &'chain CoordinatorChain,
2274    frame: &'frame_ref CoordinatorFrame<'frame_data>,
2275    index: usize,
2276}
2277
2278impl Measurable for CoordinatorLink<'_, '_, '_> {
2279    fn measure(&self, constraints: Constraints) -> Placeable {
2280        self.chain.measure_from(self.index, self.frame, constraints)
2281    }
2282
2283    fn min_intrinsic_width(&self, height: f32) -> f32 {
2284        self.chain
2285            .min_intrinsic_width_from(self.index, self.frame, height)
2286    }
2287
2288    fn max_intrinsic_width(&self, height: f32) -> f32 {
2289        self.chain
2290            .max_intrinsic_width_from(self.index, self.frame, height)
2291    }
2292
2293    fn min_intrinsic_height(&self, width: f32) -> f32 {
2294        self.chain
2295            .min_intrinsic_height_from(self.index, self.frame, width)
2296    }
2297
2298    fn max_intrinsic_height(&self, width: f32) -> f32 {
2299        self.chain
2300            .max_intrinsic_height_from(self.index, self.frame, width)
2301    }
2302}
2303
2304struct CoordinatorNode {
2305    modifier_index: usize,
2306    node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2307    measured_size: Cell<Size>,
2308    accumulated_offset: Cell<Point>,
2309}
2310
2311impl CoordinatorNode {
2312    fn new(
2313        modifier_index: usize,
2314        node: Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2315    ) -> Self {
2316        Self {
2317            modifier_index,
2318            node,
2319            measured_size: Cell::new(Size::default()),
2320            accumulated_offset: Cell::new(Point::default()),
2321        }
2322    }
2323
2324    fn matches(
2325        &self,
2326        modifier_index: usize,
2327        node: &Rc<RefCell<Box<dyn cranpose_foundation::ModifierNode>>>,
2328    ) -> bool {
2329        self.modifier_index == modifier_index && Rc::ptr_eq(&self.node, node)
2330    }
2331
2332    #[cfg(test)]
2333    fn ptr(&self) -> usize {
2334        Rc::as_ptr(&self.node) as *const () as usize
2335    }
2336}
2337
2338#[derive(Default)]
2339struct CoordinatorChain {
2340    nodes: Vec<CoordinatorNode>,
2341}
2342
2343impl CoordinatorChain {
2344    fn reconcile(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2345        if self.matches(layout_node_data) {
2346            return;
2347        }
2348
2349        let mut previous_nodes = std::mem::take(&mut self.nodes);
2350        self.nodes.reserve(layout_node_data.len());
2351
2352        for (modifier_index, node) in layout_node_data.iter() {
2353            if let Some(position) = previous_nodes
2354                .iter()
2355                .position(|candidate| candidate.matches(*modifier_index, node))
2356            {
2357                self.nodes.push(previous_nodes.swap_remove(position));
2358            } else {
2359                self.nodes
2360                    .push(CoordinatorNode::new(*modifier_index, Rc::clone(node)));
2361            }
2362        }
2363    }
2364
2365    fn matches(&self, layout_node_data: &[LayoutModifierNodeData]) -> bool {
2366        self.nodes.len() == layout_node_data.len()
2367            && self
2368                .nodes
2369                .iter()
2370                .zip(layout_node_data.iter())
2371                .all(|(node, (modifier_index, node_rc))| node.matches(*modifier_index, node_rc))
2372    }
2373
2374    fn measure_from(
2375        &self,
2376        index: usize,
2377        frame: &CoordinatorFrame<'_>,
2378        constraints: Constraints,
2379    ) -> Placeable {
2380        let Some(node) = self.nodes.get(index) else {
2381            let mut placements = frame.placements.borrow_mut();
2382            let size =
2383                frame
2384                    .measure_policy
2385                    .measure_into(frame.measurables, constraints, &mut placements);
2386            return Placeable::value(size.width, size.height, NodeId::default());
2387        };
2388
2389        let wrapped = CoordinatorLink {
2390            chain: self,
2391            frame,
2392            index: index + 1,
2393        };
2394        let node_borrow = node.node.borrow();
2395
2396        let Some(layout_node) = node_borrow.as_layout_node() else {
2397            let placeable = wrapped.measure(constraints);
2398            let child_accumulated = self.total_content_offset_from(index + 1);
2399            node.accumulated_offset.set(child_accumulated);
2400            return Placeable::value_with_offset(
2401                placeable.width(),
2402                placeable.height(),
2403                NodeId::default(),
2404                (child_accumulated.x, child_accumulated.y),
2405            );
2406        };
2407
2408        let result = match frame.context.try_borrow_mut() {
2409            Ok(mut context) => layout_node.measure(&mut *context, &wrapped, constraints),
2410            Err(_) => {
2411                let mut temp = LayoutNodeContext::new();
2412                let result = layout_node.measure(&mut temp, &wrapped, constraints);
2413                if let Ok(mut context) = frame.context.try_borrow_mut() {
2414                    for kind in temp.take_invalidations() {
2415                        context.invalidate(kind);
2416                    }
2417                }
2418                result
2419            }
2420        };
2421
2422        node.measured_size.set(result.size);
2423        let local_offset = Point {
2424            x: result.placement_offset_x,
2425            y: result.placement_offset_y,
2426        };
2427        let child_accumulated = self.total_content_offset_from(index + 1);
2428        let accumulated = Point {
2429            x: local_offset.x + child_accumulated.x,
2430            y: local_offset.y + child_accumulated.y,
2431        };
2432        node.accumulated_offset.set(accumulated);
2433
2434        Placeable::value_with_offset(
2435            result.size.width,
2436            result.size.height,
2437            NodeId::default(),
2438            (accumulated.x, accumulated.y),
2439        )
2440    }
2441
2442    fn min_intrinsic_width_from(
2443        &self,
2444        index: usize,
2445        frame: &CoordinatorFrame<'_>,
2446        height: f32,
2447    ) -> f32 {
2448        let Some(node) = self.nodes.get(index) else {
2449            return frame
2450                .measure_policy
2451                .min_intrinsic_width(frame.measurables, height);
2452        };
2453        let wrapped = CoordinatorLink {
2454            chain: self,
2455            frame,
2456            index: index + 1,
2457        };
2458        let node_borrow = node.node.borrow();
2459        node_borrow
2460            .as_layout_node()
2461            .map(|layout_node| layout_node.min_intrinsic_width(&wrapped, height))
2462            .unwrap_or_else(|| wrapped.min_intrinsic_width(height))
2463    }
2464
2465    fn max_intrinsic_width_from(
2466        &self,
2467        index: usize,
2468        frame: &CoordinatorFrame<'_>,
2469        height: f32,
2470    ) -> f32 {
2471        let Some(node) = self.nodes.get(index) else {
2472            return frame
2473                .measure_policy
2474                .max_intrinsic_width(frame.measurables, height);
2475        };
2476        let wrapped = CoordinatorLink {
2477            chain: self,
2478            frame,
2479            index: index + 1,
2480        };
2481        let node_borrow = node.node.borrow();
2482        node_borrow
2483            .as_layout_node()
2484            .map(|layout_node| layout_node.max_intrinsic_width(&wrapped, height))
2485            .unwrap_or_else(|| wrapped.max_intrinsic_width(height))
2486    }
2487
2488    fn min_intrinsic_height_from(
2489        &self,
2490        index: usize,
2491        frame: &CoordinatorFrame<'_>,
2492        width: f32,
2493    ) -> f32 {
2494        let Some(node) = self.nodes.get(index) else {
2495            return frame
2496                .measure_policy
2497                .min_intrinsic_height(frame.measurables, width);
2498        };
2499        let wrapped = CoordinatorLink {
2500            chain: self,
2501            frame,
2502            index: index + 1,
2503        };
2504        let node_borrow = node.node.borrow();
2505        node_borrow
2506            .as_layout_node()
2507            .map(|layout_node| layout_node.min_intrinsic_height(&wrapped, width))
2508            .unwrap_or_else(|| wrapped.min_intrinsic_height(width))
2509    }
2510
2511    fn max_intrinsic_height_from(
2512        &self,
2513        index: usize,
2514        frame: &CoordinatorFrame<'_>,
2515        width: f32,
2516    ) -> f32 {
2517        let Some(node) = self.nodes.get(index) else {
2518            return frame
2519                .measure_policy
2520                .max_intrinsic_height(frame.measurables, width);
2521        };
2522        let wrapped = CoordinatorLink {
2523            chain: self,
2524            frame,
2525            index: index + 1,
2526        };
2527        let node_borrow = node.node.borrow();
2528        node_borrow
2529            .as_layout_node()
2530            .map(|layout_node| layout_node.max_intrinsic_height(&wrapped, width))
2531            .unwrap_or_else(|| wrapped.max_intrinsic_height(width))
2532    }
2533
2534    fn total_content_offset_from(&self, index: usize) -> Point {
2535        self.nodes
2536            .get(index)
2537            .map(|node| node.accumulated_offset.get())
2538            .unwrap_or_default()
2539    }
2540
2541    #[cfg(test)]
2542    fn debug_ptrs(&self) -> Vec<usize> {
2543        self.nodes.iter().map(CoordinatorNode::ptr).collect()
2544    }
2545}
2546
2547#[derive(Default)]
2548pub(crate) struct LayoutRuntimeState {
2549    child_ids: Vec<NodeId>,
2550    child_states: Vec<Rc<LayoutChildMeasureState>>,
2551    child_measurables: Vec<Box<dyn Measurable>>,
2552    coordinator_chain: CoordinatorChain,
2553}
2554
2555impl LayoutRuntimeState {
2556    fn reconcile_child_measurables(&mut self, child_ids: &[NodeId]) {
2557        if self.child_ids == child_ids {
2558            return;
2559        }
2560
2561        let mut previous_ids = std::mem::take(&mut self.child_ids);
2562        let mut previous_states = std::mem::take(&mut self.child_states);
2563        let mut previous_measurables = std::mem::take(&mut self.child_measurables);
2564
2565        self.child_ids.reserve(child_ids.len());
2566        self.child_states.reserve(child_ids.len());
2567        self.child_measurables.reserve(child_ids.len());
2568
2569        for &child_id in child_ids {
2570            if let Some(position) = previous_ids.iter().position(|&id| id == child_id) {
2571                self.child_ids.push(previous_ids.swap_remove(position));
2572                self.child_states
2573                    .push(previous_states.swap_remove(position));
2574                self.child_measurables
2575                    .push(previous_measurables.swap_remove(position));
2576            } else {
2577                let state = LayoutChildMeasureState::new(child_id);
2578                self.child_ids.push(child_id);
2579                self.child_states.push(Rc::clone(&state));
2580                self.child_measurables
2581                    .push(Box::new(LayoutChildMeasurable::new(state)));
2582            }
2583        }
2584    }
2585
2586    fn child_state(&self, index: usize) -> Rc<LayoutChildMeasureState> {
2587        Rc::clone(&self.child_states[index])
2588    }
2589
2590    fn child_measurables(&self) -> &[Box<dyn Measurable>] {
2591        self.child_measurables.as_slice()
2592    }
2593
2594    fn reconcile_coordinator_chain(&mut self, layout_node_data: &[LayoutModifierNodeData]) {
2595        self.coordinator_chain.reconcile(layout_node_data);
2596    }
2597
2598    fn coordinator_chain(&self) -> &CoordinatorChain {
2599        &self.coordinator_chain
2600    }
2601
2602    fn clear_frame_bindings(&self) {
2603        for child_state in &self.child_states {
2604            child_state.clear_frame_bindings();
2605        }
2606    }
2607
2608    #[cfg(test)]
2609    pub(crate) fn debug_stats(&self) -> LayoutRuntimeDebugStats {
2610        LayoutRuntimeDebugStats {
2611            child_ids: self.child_ids.clone(),
2612            child_state_ptrs: self
2613                .child_states
2614                .iter()
2615                .map(|state| Rc::as_ptr(state) as *const () as usize)
2616                .collect(),
2617            child_measurable_ptrs: self
2618                .child_measurables
2619                .iter()
2620                .map(|measurable| {
2621                    measurable.as_ref() as *const dyn Measurable as *const () as usize
2622                })
2623                .collect(),
2624            child_measurable_count: self.child_measurables.len(),
2625            coordinator_node_ptrs: self.coordinator_chain.debug_ptrs(),
2626            coordinator_node_count: self.coordinator_chain.nodes.len(),
2627        }
2628    }
2629}
2630
2631#[cfg(test)]
2632#[derive(Debug, Clone, PartialEq, Eq)]
2633pub(crate) struct LayoutRuntimeDebugStats {
2634    pub(crate) child_ids: Vec<NodeId>,
2635    pub(crate) child_state_ptrs: Vec<usize>,
2636    pub(crate) child_measurable_ptrs: Vec<usize>,
2637    pub(crate) child_measurable_count: usize,
2638    pub(crate) coordinator_node_ptrs: Vec<usize>,
2639    pub(crate) coordinator_node_count: usize,
2640}
2641
2642struct LayoutChildMeasureConfig {
2643    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
2644    node_id: NodeId,
2645    error: Rc<RefCell<Option<NodeError>>>,
2646    runtime_handle: Option<RuntimeHandle>,
2647    cache: LayoutNodeCacheHandles,
2648    cache_epoch: u64,
2649    force_remeasure: bool,
2650    measure_handle: Option<LayoutMeasureHandle>,
2651    layout_state: Option<Rc<RefCell<LayoutState>>>,
2652}
2653
2654struct LayoutChildMeasureState {
2655    applier: RefCell<Option<Rc<ConcreteApplierHost<MemoryApplier>>>>,
2656    node_id: Cell<NodeId>,
2657    measured: RefCell<Option<Rc<MeasuredNode>>>,
2658    last_position: Cell<Option<Point>>,
2659    error: RefCell<Option<Rc<RefCell<Option<NodeError>>>>>,
2660    runtime_handle: RefCell<Option<RuntimeHandle>>,
2661    cache: RefCell<LayoutNodeCacheHandles>,
2662    cache_epoch: Cell<u64>,
2663    force_remeasure: Cell<bool>,
2664    measure_handle: RefCell<Option<LayoutMeasureHandle>>,
2665    layout_state: RefCell<Option<Rc<RefCell<LayoutState>>>>,
2666}
2667
2668impl LayoutChildMeasureState {
2669    fn new(node_id: NodeId) -> Rc<Self> {
2670        Rc::new(Self {
2671            applier: RefCell::new(None),
2672            node_id: Cell::new(node_id),
2673            measured: RefCell::new(None),
2674            last_position: Cell::new(None),
2675            error: RefCell::new(None),
2676            runtime_handle: RefCell::new(None),
2677            cache: RefCell::new(LayoutNodeCacheHandles::default()),
2678            cache_epoch: Cell::new(0),
2679            force_remeasure: Cell::new(true),
2680            measure_handle: RefCell::new(None),
2681            layout_state: RefCell::new(None),
2682        })
2683    }
2684
2685    fn configure(&self, config: LayoutChildMeasureConfig) {
2686        config.cache.activate(config.cache_epoch);
2687        *self.applier.borrow_mut() = Some(config.applier);
2688        self.node_id.set(config.node_id);
2689        self.measured.borrow_mut().take();
2690        self.last_position.set(None);
2691        *self.error.borrow_mut() = Some(config.error);
2692        *self.runtime_handle.borrow_mut() = config.runtime_handle;
2693        *self.cache.borrow_mut() = config.cache;
2694        self.cache_epoch.set(config.cache_epoch);
2695        self.force_remeasure.set(config.force_remeasure);
2696        *self.measure_handle.borrow_mut() = config.measure_handle;
2697        *self.layout_state.borrow_mut() = config.layout_state;
2698    }
2699
2700    fn clear_frame_bindings(&self) {
2701        self.measured.borrow_mut().take();
2702        *self.applier.borrow_mut() = None;
2703        *self.error.borrow_mut() = None;
2704        *self.runtime_handle.borrow_mut() = None;
2705        *self.measure_handle.borrow_mut() = None;
2706        *self.layout_state.borrow_mut() = None;
2707    }
2708
2709    fn node_id(&self) -> NodeId {
2710        self.node_id.get()
2711    }
2712
2713    fn cache(&self) -> LayoutNodeCacheHandles {
2714        self.cache.borrow().clone()
2715    }
2716
2717    fn applier(&self) -> Option<Rc<ConcreteApplierHost<MemoryApplier>>> {
2718        self.applier.borrow().clone()
2719    }
2720
2721    fn layout_state(&self) -> Option<Rc<RefCell<LayoutState>>> {
2722        self.layout_state.borrow().clone()
2723    }
2724
2725    fn take_measured(&self) -> Option<Rc<MeasuredNode>> {
2726        self.measured.borrow_mut().take()
2727    }
2728
2729    fn last_position(&self) -> Option<Point> {
2730        self.last_position.get()
2731    }
2732
2733    fn set_last_position(&self, position: Point) {
2734        self.last_position.set(Some(position));
2735    }
2736
2737    fn set_measured(&self, measured: Option<Rc<MeasuredNode>>) {
2738        *self.measured.borrow_mut() = measured;
2739    }
2740
2741    fn record_error(&self, err: NodeError) {
2742        let Some(error) = self.error.borrow().clone() else {
2743            return;
2744        };
2745        let mut slot = error.borrow_mut();
2746        if slot.is_none() {
2747            *slot = Some(err);
2748        }
2749    }
2750
2751    fn perform_measure(&self, constraints: Constraints) -> Result<Rc<MeasuredNode>, NodeError> {
2752        let node_id = self.node_id();
2753        if let Some(handle) = self.measure_handle.borrow().clone() {
2754            return handle.measure(node_id, constraints);
2755        }
2756        let applier = self.applier().ok_or(NodeError::MissingContext {
2757            id: node_id,
2758            reason: "layout child applier not configured",
2759        })?;
2760        measure_node_with_host(
2761            applier,
2762            self.runtime_handle.borrow().clone(),
2763            node_id,
2764            constraints,
2765            self.cache_epoch.get(),
2766        )
2767    }
2768
2769    fn intrinsic_measure(&self, constraints: Constraints) -> Option<Rc<MeasuredNode>> {
2770        let cache = self.cache();
2771        cache.activate(self.cache_epoch.get());
2772        if !self.force_remeasure.get() {
2773            if let Some(cached) = cache.get_measurement(constraints) {
2774                return Some(cached);
2775            }
2776        }
2777
2778        match self.perform_measure(constraints) {
2779            Ok(measured) => {
2780                self.force_remeasure.set(false);
2781                cache.store_measurement(constraints, Rc::clone(&measured));
2782                Some(measured)
2783            }
2784            Err(err) => {
2785                self.record_error(err);
2786                None
2787            }
2788        }
2789    }
2790}
2791
2792struct LayoutChildMeasurable {
2793    state: Rc<LayoutChildMeasureState>,
2794}
2795
2796impl LayoutChildMeasurable {
2797    fn new(state: Rc<LayoutChildMeasureState>) -> Self {
2798        Self { state }
2799    }
2800}
2801
2802impl Measurable for LayoutChildMeasurable {
2803    fn measure(&self, constraints: Constraints) -> Placeable {
2804        let state = &self.state;
2805        let cache = state.cache();
2806        cache.activate(state.cache_epoch.get());
2807        let measured_size;
2808        if !state.force_remeasure.get() {
2809            if let Some(cached) = cache.get_measurement(constraints) {
2810                measured_size = cached.size;
2811                state.set_measured(Some(Rc::clone(&cached)));
2812            } else {
2813                match state.perform_measure(constraints) {
2814                    Ok(measured) => {
2815                        state.force_remeasure.set(false);
2816                        measured_size = measured.size;
2817                        cache.store_measurement(constraints, Rc::clone(&measured));
2818                        state.set_measured(Some(measured));
2819                    }
2820                    Err(err) => {
2821                        state.record_error(err);
2822                        state.set_measured(None);
2823                        measured_size = Size {
2824                            width: 0.0,
2825                            height: 0.0,
2826                        };
2827                    }
2828                }
2829            }
2830        } else {
2831            match state.perform_measure(constraints) {
2832                Ok(measured) => {
2833                    state.force_remeasure.set(false);
2834                    measured_size = measured.size;
2835                    cache.store_measurement(constraints, Rc::clone(&measured));
2836                    state.set_measured(Some(measured));
2837                }
2838                Err(err) => {
2839                    state.record_error(err);
2840                    state.set_measured(None);
2841                    measured_size = Size {
2842                        width: 0.0,
2843                        height: 0.0,
2844                    };
2845                }
2846            }
2847        }
2848
2849        if let Some(layout_state) = state.layout_state() {
2850            let mut layout_state = layout_state.borrow_mut();
2851            layout_state.size = measured_size;
2852            layout_state.measurement_constraints = constraints;
2853        } else if let Some(applier) = state.applier() {
2854            let Ok(mut applier) = applier.try_borrow_typed() else {
2855                return Placeable::value(
2856                    measured_size.width,
2857                    measured_size.height,
2858                    state.node_id(),
2859                );
2860            };
2861            let _ = applier.with_node::<LayoutNode, _>(state.node_id(), |node| {
2862                node.set_measured_size(measured_size);
2863                node.set_measurement_constraints(constraints);
2864            });
2865        }
2866
2867        let state = Rc::clone(&self.state);
2868        let applier = state.applier();
2869        let node_id = state.node_id();
2870        let layout_state = state.layout_state();
2871
2872        let place_fn = Rc::new(move |x: f32, y: f32| {
2873            let internal_offset = state
2874                .measured
2875                .borrow()
2876                .as_ref()
2877                .map(|m| m.offset)
2878                .unwrap_or_default();
2879
2880            let position = Point {
2881                x: x + internal_offset.x,
2882                y: y + internal_offset.y,
2883            };
2884            state.set_last_position(position);
2885
2886            if let Some(layout_state) = &layout_state {
2887                let mut layout_state = layout_state.borrow_mut();
2888                layout_state.position = position;
2889                layout_state.is_placed = true;
2890            } else if let Some(applier) = &applier {
2891                let Ok(mut applier) = applier.try_borrow_typed() else {
2892                    return;
2893                };
2894                if applier
2895                    .with_node::<LayoutNode, _>(node_id, |node| {
2896                        node.set_position(position);
2897                    })
2898                    .is_err()
2899                {
2900                    let _ = applier.with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
2901                        node.set_position(position);
2902                    });
2903                }
2904            }
2905        });
2906
2907        Placeable::with_place_fn(measured_size.width, measured_size.height, node_id, place_fn)
2908    }
2909
2910    fn min_intrinsic_width(&self, height: f32) -> f32 {
2911        let kind = IntrinsicKind::MinWidth(height);
2912        let cache = self.state.cache();
2913        cache.activate(self.state.cache_epoch.get());
2914        if !self.state.force_remeasure.get() {
2915            if let Some(value) = cache.get_intrinsic(&kind) {
2916                return value;
2917            }
2918        }
2919        let constraints = Constraints {
2920            min_width: 0.0,
2921            max_width: f32::INFINITY,
2922            min_height: height,
2923            max_height: height,
2924        };
2925        if let Some(node) = self.state.intrinsic_measure(constraints) {
2926            let value = node.size.width;
2927            cache.store_intrinsic(kind, value);
2928            value
2929        } else {
2930            0.0
2931        }
2932    }
2933
2934    fn max_intrinsic_width(&self, height: f32) -> f32 {
2935        let kind = IntrinsicKind::MaxWidth(height);
2936        let cache = self.state.cache();
2937        cache.activate(self.state.cache_epoch.get());
2938        if !self.state.force_remeasure.get() {
2939            if let Some(value) = cache.get_intrinsic(&kind) {
2940                return value;
2941            }
2942        }
2943        let constraints = Constraints {
2944            min_width: 0.0,
2945            max_width: f32::INFINITY,
2946            min_height: 0.0,
2947            max_height: height,
2948        };
2949        if let Some(node) = self.state.intrinsic_measure(constraints) {
2950            let value = node.size.width;
2951            cache.store_intrinsic(kind, value);
2952            value
2953        } else {
2954            0.0
2955        }
2956    }
2957
2958    fn min_intrinsic_height(&self, width: f32) -> f32 {
2959        let kind = IntrinsicKind::MinHeight(width);
2960        let cache = self.state.cache();
2961        cache.activate(self.state.cache_epoch.get());
2962        if !self.state.force_remeasure.get() {
2963            if let Some(value) = cache.get_intrinsic(&kind) {
2964                return value;
2965            }
2966        }
2967        let constraints = Constraints {
2968            min_width: width,
2969            max_width: width,
2970            min_height: 0.0,
2971            max_height: f32::INFINITY,
2972        };
2973        if let Some(node) = self.state.intrinsic_measure(constraints) {
2974            let value = node.size.height;
2975            cache.store_intrinsic(kind, value);
2976            value
2977        } else {
2978            0.0
2979        }
2980    }
2981
2982    fn max_intrinsic_height(&self, width: f32) -> f32 {
2983        let kind = IntrinsicKind::MaxHeight(width);
2984        let cache = self.state.cache();
2985        cache.activate(self.state.cache_epoch.get());
2986        if !self.state.force_remeasure.get() {
2987            if let Some(value) = cache.get_intrinsic(&kind) {
2988                return value;
2989            }
2990        }
2991        let constraints = Constraints {
2992            min_width: 0.0,
2993            max_width: width,
2994            min_height: 0.0,
2995            max_height: f32::INFINITY,
2996        };
2997        if let Some(node) = self.state.intrinsic_measure(constraints) {
2998            let value = node.size.height;
2999            cache.store_intrinsic(kind, value);
3000            value
3001        } else {
3002            0.0
3003        }
3004    }
3005
3006    fn flex_parent_data(&self) -> Option<cranpose_ui_layout::FlexParentData> {
3007        let applier = self.state.applier()?;
3008        let node_id = self.state.node_id();
3009        let Ok(mut applier) = applier.try_borrow_typed() else {
3010            return None;
3011        };
3012
3013        applier
3014            .with_node::<LayoutNode, _>(node_id, |layout_node| {
3015                let props = layout_node.resolved_modifiers().layout_properties();
3016                props.weight().map(|weight_data| {
3017                    cranpose_ui_layout::FlexParentData::new(weight_data.weight, weight_data.fill)
3018                })
3019            })
3020            .ok()
3021            .flatten()
3022    }
3023}
3024
3025fn measure_node_with_host(
3026    applier: Rc<ConcreteApplierHost<MemoryApplier>>,
3027    runtime_handle: Option<RuntimeHandle>,
3028    node_id: NodeId,
3029    constraints: Constraints,
3030    epoch: u64,
3031) -> Result<Rc<MeasuredNode>, NodeError> {
3032    let runtime_handle = match runtime_handle {
3033        Some(handle) => Some(handle),
3034        None => applier.borrow_typed().runtime_handle(),
3035    };
3036    let mut builder = LayoutBuilder::new_with_epoch(
3037        applier,
3038        epoch,
3039        Rc::new(RefCell::new(SlotTable::default())),
3040        FrameLayoutArena::default(),
3041    );
3042    builder.set_runtime_handle(runtime_handle);
3043    builder.measure_node(node_id, constraints)
3044}
3045
3046#[derive(Clone)]
3047struct RuntimeNodeMetadata {
3048    modifier: Modifier,
3049    resolved_modifiers: ResolvedModifiers,
3050    modifier_slices: Rc<ModifierNodeSlices>,
3051    role: SemanticsRole,
3052    button_handler: Option<Rc<RefCell<dyn FnMut()>>>,
3053}
3054
3055impl Default for RuntimeNodeMetadata {
3056    fn default() -> Self {
3057        Self {
3058            modifier: Modifier::empty(),
3059            resolved_modifiers: ResolvedModifiers::default(),
3060            modifier_slices: Rc::default(),
3061            role: SemanticsRole::Unknown,
3062            button_handler: None,
3063        }
3064    }
3065}
3066
3067fn role_from_modifier_slices(modifier_slices: &ModifierNodeSlices) -> SemanticsRole {
3068    modifier_slices
3069        .text_content()
3070        .map(|text| SemanticsRole::Text {
3071            value: text.to_string(),
3072        })
3073        .unwrap_or(SemanticsRole::Layout)
3074}
3075
3076fn runtime_metadata_for(
3077    applier: &mut MemoryApplier,
3078    node_id: NodeId,
3079) -> Result<RuntimeNodeMetadata, NodeError> {
3080    // Try LayoutNode (the primary modern path)
3081    // IMPORTANT: We use with_node (reference) instead of try_clone because cloning
3082    // LayoutNode creates a NEW ModifierChainHandle with NEW nodes and NEW handlers,
3083    // which would lose gesture state like press_position.
3084    if let Ok(meta) = applier.with_node::<LayoutNode, _>(node_id, |layout| {
3085        let modifier = layout.modifier.clone();
3086        let resolved_modifiers = layout.resolved_modifiers();
3087        let modifier_slices = layout.modifier_slices_snapshot();
3088        let role = role_from_modifier_slices(&modifier_slices);
3089
3090        RuntimeNodeMetadata {
3091            modifier,
3092            resolved_modifiers,
3093            modifier_slices,
3094            role,
3095            button_handler: None,
3096        }
3097    }) {
3098        return Ok(meta);
3099    }
3100
3101    // Try SubcomposeLayoutNode
3102    if let Ok((modifier, resolved_modifiers, modifier_slices)) = applier
3103        .with_node::<SubcomposeLayoutNode, _>(node_id, |node| {
3104            (
3105                node.modifier(),
3106                node.resolved_modifiers(),
3107                node.modifier_slices_snapshot(),
3108            )
3109        })
3110    {
3111        return Ok(RuntimeNodeMetadata {
3112            modifier,
3113            resolved_modifiers,
3114            modifier_slices,
3115            role: SemanticsRole::Subcompose,
3116            button_handler: None,
3117        });
3118    }
3119    Ok(RuntimeNodeMetadata::default())
3120}
3121
3122fn clear_semantics_dirty_flags(
3123    applier: &mut MemoryApplier,
3124    node: &MeasuredNode,
3125) -> Result<(), NodeError> {
3126    match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3127        layout.clear_needs_semantics();
3128    }) {
3129        Ok(()) => {}
3130        Err(NodeError::Missing { .. }) => {}
3131        Err(NodeError::TypeMismatch { .. }) => {
3132            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3133                subcompose.clear_needs_semantics();
3134            }) {
3135                Ok(()) | Err(NodeError::Missing { .. }) | Err(NodeError::TypeMismatch { .. }) => {}
3136                Err(err) => return Err(err),
3137            }
3138        }
3139        Err(err) => return Err(err),
3140    }
3141
3142    for child in &node.children {
3143        clear_semantics_dirty_flags(applier, &child.node)?;
3144    }
3145
3146    Ok(())
3147}
3148
3149fn build_semantics_tree_from_live_nodes(
3150    applier: &mut MemoryApplier,
3151    node: &MeasuredNode,
3152) -> Result<SemanticsTree, NodeError> {
3153    Ok(SemanticsTree::new(build_semantics_node_from_live_nodes(
3154        applier, node,
3155    )?))
3156}
3157
3158fn semantics_node_from_parts(
3159    node_id: NodeId,
3160    mut role: SemanticsRole,
3161    config: Option<SemanticsConfiguration>,
3162    children: Vec<SemanticsNode>,
3163) -> SemanticsNode {
3164    let mut actions = Vec::new();
3165    let mut description = None;
3166    let mut editable_text = false;
3167    let mut text_selection = None;
3168
3169    if let Some(config) = config {
3170        if config.is_button {
3171            role = SemanticsRole::Button;
3172        }
3173        if config.is_clickable {
3174            actions.push(SemanticsAction::Click {
3175                handler: SemanticsCallback::new(node_id),
3176            });
3177        }
3178        description = config.content_description;
3179        editable_text = config.is_editable_text;
3180        text_selection = config.text_selection;
3181    }
3182
3183    SemanticsNode::new(
3184        node_id,
3185        role,
3186        actions,
3187        children,
3188        description,
3189        editable_text,
3190        text_selection,
3191    )
3192}
3193
3194fn build_semantics_node_from_live_nodes(
3195    applier: &mut MemoryApplier,
3196    node: &MeasuredNode,
3197) -> Result<SemanticsNode, NodeError> {
3198    let (role, config) = match applier.with_node::<LayoutNode, _>(node.node_id, |layout| {
3199        let role = role_from_modifier_slices(&layout.modifier_slices_snapshot());
3200        let config = layout.semantics_configuration();
3201        layout.clear_needs_semantics();
3202        (role, config)
3203    }) {
3204        Ok(data) => data,
3205        Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
3206            match applier.with_node::<SubcomposeLayoutNode, _>(node.node_id, |subcompose| {
3207                subcompose.clear_needs_semantics();
3208                (
3209                    SemanticsRole::Subcompose,
3210                    collect_semantics_from_modifier(&subcompose.modifier()),
3211                )
3212            }) {
3213                Ok(data) => data,
3214                Err(NodeError::TypeMismatch { .. }) | Err(NodeError::Missing { .. }) => {
3215                    (SemanticsRole::Unknown, None)
3216                }
3217                Err(err) => return Err(err),
3218            }
3219        }
3220        Err(err) => return Err(err),
3221    };
3222
3223    let mut children = Vec::with_capacity(node.children.len());
3224    for child in &node.children {
3225        children.push(build_semantics_node_from_live_nodes(applier, &child.node)?);
3226    }
3227
3228    Ok(semantics_node_from_parts(
3229        node.node_id,
3230        role,
3231        config,
3232        children,
3233    ))
3234}
3235
3236fn record_semantics_allocation_stats(node: &SemanticsNode, stats: &mut LayoutAllocationDebugStats) {
3237    stats.semantics_node_count += 1;
3238    stats.semantics_action_count += node.actions.len();
3239    stats.semantics_action_capacity += node.actions.capacity();
3240    stats.semantics_child_count += node.children.len();
3241    stats.semantics_child_capacity += node.children.capacity();
3242    stats.semantics_heap_bytes += node.actions.capacity() * size_of::<SemanticsAction>();
3243    stats.semantics_heap_bytes += node.children.capacity() * size_of::<SemanticsNode>();
3244
3245    if let Some(description) = &node.description {
3246        stats.semantics_description_count += 1;
3247        stats.semantics_description_bytes += description.capacity();
3248        stats.semantics_heap_bytes += description.capacity();
3249    }
3250    if let SemanticsRole::Text { value } = &node.role {
3251        stats.semantics_text_role_bytes += value.capacity();
3252        stats.semantics_heap_bytes += value.capacity();
3253    }
3254
3255    for child in &node.children {
3256        record_semantics_allocation_stats(child, stats);
3257    }
3258}
3259
3260fn record_layout_box_allocation_stats(
3261    layout_box: &LayoutBox,
3262    stats: &mut LayoutAllocationDebugStats,
3263) {
3264    stats.layout_box_count += 1;
3265    stats.layout_box_child_count += layout_box.children.len();
3266    stats.layout_box_child_capacity += layout_box.children.capacity();
3267    stats.layout_box_heap_bytes += layout_box.children.capacity() * size_of::<LayoutBox>();
3268    stats.add_modifier_slice(layout_box.node_data.modifier_slices().debug_stats());
3269
3270    for child in &layout_box.children {
3271        record_layout_box_allocation_stats(child, stats);
3272    }
3273}
3274
3275fn build_layout_tree(
3276    applier: &mut MemoryApplier,
3277    node: &MeasuredNode,
3278) -> Result<LayoutTree, NodeError> {
3279    fn place(
3280        applier: &mut MemoryApplier,
3281        node: &MeasuredNode,
3282        origin: Point,
3283        // Accumulated ancestor graphics-layer translation (window px): a node's
3284        // drawn content is shifted by every ancestor layer's translation on top
3285        // of its layout position, so a text field's TRUE on-screen origin adds
3286        // this. Scroll offsets are already baked into `origin` via placement;
3287        // this carries the extra translation-transform component. Scale/rotation
3288        // are not folded in (handle placement under a zoom layer is a documented
3289        // gap).
3290        parent_layer_translation: Point,
3291    ) -> Result<LayoutBox, NodeError> {
3292        // Include the node's own offset (from OffsetNode) in its position
3293        let top_left = Point {
3294            x: origin.x + node.offset.x,
3295            y: origin.y + node.offset.y,
3296        };
3297        let rect = GeometryRect {
3298            x: top_left.x,
3299            y: top_left.y,
3300            width: node.size.width,
3301            height: node.size.height,
3302        };
3303        let info = runtime_metadata_for(applier, node.node_id)?;
3304        let kind = layout_kind_from_metadata(node.node_id, &info);
3305        let RuntimeNodeMetadata {
3306            modifier,
3307            resolved_modifiers,
3308            modifier_slices,
3309            ..
3310        } = info;
3311
3312        let layer_translation = match modifier_slices.graphics_layer() {
3313            Some(layer) => Point {
3314                x: parent_layer_translation.x + layer.translation_x,
3315                y: parent_layer_translation.y + layer.translation_y,
3316            },
3317            None => parent_layer_translation,
3318        };
3319
3320        // Publish the field's TRUE composited window origin for its finger
3321        // selection handles: layout position (ancestor scroll already baked in
3322        // via placement) + accumulated graphics-layer translation. Re-read every
3323        // layout pass so the handles (and their window→offset inverse mapping)
3324        // track the field live as an enclosing list scrolls.
3325        if let Some(sink) = modifier_slices.text_field_window_origin() {
3326            sink.set(Point {
3327                x: top_left.x + layer_translation.x,
3328                y: top_left.y + layer_translation.y,
3329            });
3330        }
3331
3332        // Publish a scroll container's composited viewport rect (window
3333        // coordinates) for its `BringIntoViewResponder`.
3334        if let Some(sink) = modifier_slices.viewport_window_rect() {
3335            sink.set(GeometryRect {
3336                x: top_left.x + layer_translation.x,
3337                y: top_left.y + layer_translation.y,
3338                width: node.size.width,
3339                height: node.size.height,
3340            });
3341        }
3342
3343        let data = LayoutNodeData::new(modifier, resolved_modifiers, modifier_slices, kind);
3344        let mut children = Vec::with_capacity(node.children.len());
3345        for child in &node.children {
3346            let child_origin = Point {
3347                x: top_left.x + child.offset.x,
3348                y: top_left.y + child.offset.y,
3349            };
3350            children.push(place(
3351                applier,
3352                &child.node,
3353                child_origin,
3354                layer_translation,
3355            )?);
3356        }
3357        Ok(LayoutBox::new(
3358            node.node_id,
3359            rect,
3360            node.content_offset,
3361            data,
3362            children,
3363        ))
3364    }
3365
3366    Ok(LayoutTree::new(place(
3367        applier,
3368        node,
3369        Point { x: 0.0, y: 0.0 },
3370        Point { x: 0.0, y: 0.0 },
3371    )?))
3372}
3373
3374fn semantics_role_from_layout_box(layout_box: &LayoutBox) -> SemanticsRole {
3375    match &layout_box.node_data.kind {
3376        LayoutNodeKind::Subcompose => SemanticsRole::Subcompose,
3377        LayoutNodeKind::Spacer => SemanticsRole::Spacer,
3378        LayoutNodeKind::Unknown => SemanticsRole::Unknown,
3379        LayoutNodeKind::Button { .. } => SemanticsRole::Button,
3380        LayoutNodeKind::Layout => layout_box
3381            .node_data
3382            .modifier_slices()
3383            .text_content()
3384            .map(|text| SemanticsRole::Text {
3385                value: text.to_string(),
3386            })
3387            .unwrap_or(SemanticsRole::Layout),
3388    }
3389}
3390
3391fn build_semantics_node_from_layout_box(layout_box: &LayoutBox) -> SemanticsNode {
3392    let children = layout_box
3393        .children
3394        .iter()
3395        .map(build_semantics_node_from_layout_box)
3396        .collect();
3397
3398    semantics_node_from_parts(
3399        layout_box.node_id,
3400        semantics_role_from_layout_box(layout_box),
3401        collect_semantics_from_modifier(&layout_box.node_data.modifier),
3402        children,
3403    )
3404}
3405
3406fn layout_kind_from_metadata(_node_id: NodeId, info: &RuntimeNodeMetadata) -> LayoutNodeKind {
3407    match &info.role {
3408        SemanticsRole::Layout => LayoutNodeKind::Layout,
3409        SemanticsRole::Subcompose => LayoutNodeKind::Subcompose,
3410        SemanticsRole::Text { .. } => {
3411            // Text content is now handled via TextModifierNode in the modifier chain
3412            // and collected in modifier_slices.text_content(). LayoutNodeKind should
3413            // reflect the layout policy (EmptyMeasurePolicy), not the content type.
3414            LayoutNodeKind::Layout
3415        }
3416        SemanticsRole::Spacer => LayoutNodeKind::Spacer,
3417        SemanticsRole::Button => {
3418            let handler = info
3419                .button_handler
3420                .as_ref()
3421                .cloned()
3422                .unwrap_or_else(|| Rc::new(RefCell::new(|| {})));
3423            LayoutNodeKind::Button { on_click: handler }
3424        }
3425        SemanticsRole::Unknown => LayoutNodeKind::Unknown,
3426    }
3427}
3428
3429fn subtract_padding(constraints: Constraints, padding: EdgeInsets) -> Constraints {
3430    let horizontal = padding.horizontal_sum();
3431    let vertical = padding.vertical_sum();
3432    let min_width = (constraints.min_width - horizontal).max(0.0);
3433    let mut max_width = constraints.max_width;
3434    if max_width.is_finite() {
3435        max_width = (max_width - horizontal).max(0.0);
3436    }
3437    let min_height = (constraints.min_height - vertical).max(0.0);
3438    let mut max_height = constraints.max_height;
3439    if max_height.is_finite() {
3440        max_height = (max_height - vertical).max(0.0);
3441    }
3442    normalize_constraints(Constraints {
3443        min_width,
3444        max_width,
3445        min_height,
3446        max_height,
3447    })
3448}
3449
3450#[cfg(test)]
3451pub(crate) fn align_horizontal(alignment: HorizontalAlignment, available: f32, child: f32) -> f32 {
3452    match alignment {
3453        HorizontalAlignment::Start => 0.0,
3454        HorizontalAlignment::CenterHorizontally => ((available - child) / 2.0).max(0.0),
3455        HorizontalAlignment::End => (available - child).max(0.0),
3456    }
3457}
3458
3459#[cfg(test)]
3460pub(crate) fn align_vertical(alignment: VerticalAlignment, available: f32, child: f32) -> f32 {
3461    match alignment {
3462        VerticalAlignment::Top => 0.0,
3463        VerticalAlignment::CenterVertically => ((available - child) / 2.0).max(0.0),
3464        VerticalAlignment::Bottom => (available - child).max(0.0),
3465    }
3466}
3467
3468fn resolve_dimension(
3469    base: f32,
3470    explicit: DimensionConstraint,
3471    min_override: Option<f32>,
3472    max_override: Option<f32>,
3473    min_limit: f32,
3474    max_limit: f32,
3475) -> f32 {
3476    let mut min_bound = min_limit;
3477    if let Some(min_value) = min_override {
3478        min_bound = min_bound.max(min_value);
3479    }
3480
3481    let mut max_bound = if max_limit.is_finite() {
3482        max_limit
3483    } else {
3484        max_override.unwrap_or(max_limit)
3485    };
3486    if let Some(max_value) = max_override {
3487        if max_bound.is_finite() {
3488            max_bound = max_bound.min(max_value);
3489        } else {
3490            max_bound = max_value;
3491        }
3492    }
3493    if max_bound < min_bound {
3494        max_bound = min_bound;
3495    }
3496
3497    let mut size = match explicit {
3498        DimensionConstraint::Points(points) => points,
3499        DimensionConstraint::Fraction(fraction) => {
3500            if max_limit.is_finite() {
3501                max_limit * fraction.clamp(0.0, 1.0)
3502            } else {
3503                base
3504            }
3505        }
3506        DimensionConstraint::Unspecified => base,
3507        // Intrinsic sizing is resolved at a higher level where we have access to children.
3508        // At this point we just use the base size as a fallback.
3509        DimensionConstraint::Intrinsic(_) => base,
3510    };
3511
3512    size = clamp_dimension(size, min_bound, max_bound);
3513    size = clamp_dimension(size, min_limit, max_limit);
3514    size.max(0.0)
3515}
3516
3517fn clamp_dimension(value: f32, min: f32, max: f32) -> f32 {
3518    let mut result = value.max(min);
3519    if max.is_finite() {
3520        result = result.min(max);
3521    }
3522    result
3523}
3524
3525fn normalize_constraints(mut constraints: Constraints) -> Constraints {
3526    if constraints.max_width < constraints.min_width {
3527        constraints.max_width = constraints.min_width;
3528    }
3529    if constraints.max_height < constraints.min_height {
3530        constraints.max_height = constraints.min_height;
3531    }
3532    constraints
3533}
3534
3535#[cfg(test)]
3536#[path = "tests/layout_tests.rs"]
3537mod tests;