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