cranpose_ui/
subcompose_layout.rs

1use std::cell::{Cell, Ref, RefCell, RefMut};
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use cranpose_core::{
6    Composer, NodeError, NodeId, Phase, SlotBackend, SlotId, SlotsHost, SubcomposeState,
7};
8use indexmap::IndexSet;
9
10use crate::modifier::{Modifier, ModifierChainHandle, Point, ResolvedModifiers, Size};
11use crate::widgets::nodes::{
12    allocate_virtual_node_id, is_virtual_node, register_layout_node, LayoutNode,
13};
14
15use cranpose_foundation::{InvalidationKind, ModifierInvalidation, NodeCapabilities};
16
17pub use cranpose_ui_layout::{Constraints, MeasureResult, Placement};
18
19/// Representation of a subcomposed child that can later be measured by the policy.
20///
21/// In lazy layouts, this represents an item that has been composed but may or
22/// may not have been measured yet. Call `measure()` to get the actual size.
23#[derive(Clone, Copy, Debug)]
24pub struct SubcomposeChild {
25    node_id: NodeId,
26    /// Measured size of the child (set after measurement).
27    /// Width in x, height in y.
28    measured_size: Option<Size>,
29}
30
31impl SubcomposeChild {
32    pub fn new(node_id: NodeId) -> Self {
33        Self {
34            node_id,
35            measured_size: None,
36        }
37    }
38
39    /// Creates a SubcomposeChild with a known size.
40    pub fn with_size(node_id: NodeId, size: Size) -> Self {
41        Self {
42            node_id,
43            measured_size: Some(size),
44        }
45    }
46
47    pub fn node_id(&self) -> NodeId {
48        self.node_id
49    }
50
51    /// Returns the measured size of this child.
52    ///
53    /// Returns a default size if the child hasn't been measured yet.
54    /// For lazy layouts using placeholder sizes, this returns the estimated size.
55    pub fn size(&self) -> Size {
56        self.measured_size.unwrap_or(Size {
57            width: 0.0,
58            height: 0.0,
59        })
60    }
61
62    /// Returns the measured width.
63    pub fn width(&self) -> f32 {
64        self.size().width
65    }
66
67    /// Returns the measured height.
68    pub fn height(&self) -> f32 {
69        self.size().height
70    }
71
72    /// Sets the measured size for this child.
73    pub fn set_size(&mut self, size: Size) {
74        self.measured_size = Some(size);
75    }
76}
77
78impl PartialEq for SubcomposeChild {
79    fn eq(&self, other: &Self) -> bool {
80        self.node_id == other.node_id
81    }
82}
83
84/// A measured child that is ready to be placed.
85#[derive(Clone, Copy, Debug)]
86pub struct SubcomposePlaceable {
87    node_id: NodeId,
88    size: Size,
89}
90
91impl SubcomposePlaceable {
92    pub fn new(node_id: NodeId, size: Size) -> Self {
93        Self { node_id, size }
94    }
95}
96
97impl cranpose_ui_layout::Placeable for SubcomposePlaceable {
98    fn place(&self, _x: f32, _y: f32) {
99        // No-op: in SubcomposeLayout, placement is handled by returning a list of Placements
100    }
101
102    fn width(&self) -> f32 {
103        self.size.width
104    }
105
106    fn height(&self) -> f32 {
107        self.size.height
108    }
109
110    fn node_id(&self) -> NodeId {
111        self.node_id
112    }
113}
114
115/// Base trait for measurement scopes.
116pub trait SubcomposeLayoutScope {
117    fn constraints(&self) -> Constraints;
118
119    fn layout<I>(&mut self, width: f32, height: f32, placements: I) -> MeasureResult
120    where
121        I: IntoIterator<Item = Placement>,
122    {
123        MeasureResult::new(Size { width, height }, placements.into_iter().collect())
124    }
125}
126
127/// Public trait exposed to measure policies for subcomposition.
128pub trait SubcomposeMeasureScope: SubcomposeLayoutScope {
129    fn subcompose<Content>(&mut self, slot_id: SlotId, content: Content) -> Vec<SubcomposeChild>
130    where
131        Content: FnOnce();
132
133    /// Measures a subcomposed child with the given constraints.
134    fn measure(&mut self, child: SubcomposeChild, constraints: Constraints) -> SubcomposePlaceable;
135
136    /// Checks if a node has no parent (is a root node).
137    /// Used to filter subcompose results to only include true root nodes.
138    fn node_has_no_parent(&self, node_id: NodeId) -> bool;
139}
140
141/// Concrete implementation of [`SubcomposeMeasureScope`].
142pub struct SubcomposeMeasureScopeImpl<'a> {
143    composer: Composer,
144    state: &'a mut SubcomposeState,
145    constraints: Constraints,
146    measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
147    error: Rc<RefCell<Option<NodeError>>>,
148    parent_handle: SubcomposeLayoutNodeHandle,
149    root_id: NodeId,
150}
151
152impl<'a> SubcomposeMeasureScopeImpl<'a> {
153    pub fn new(
154        composer: Composer,
155        state: &'a mut SubcomposeState,
156        constraints: Constraints,
157        measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
158        error: Rc<RefCell<Option<NodeError>>>,
159
160        parent_handle: SubcomposeLayoutNodeHandle,
161        root_id: NodeId,
162    ) -> Self {
163        Self {
164            composer,
165            state,
166            constraints,
167            measurer,
168            error,
169            parent_handle,
170            root_id,
171        }
172    }
173
174    fn record_error(&self, err: NodeError) {
175        let mut slot = self.error.borrow_mut();
176        if slot.is_none() {
177            eprintln!("[SubcomposeLayout] Error suppressed: {:?}", err);
178            *slot = Some(err);
179        }
180    }
181
182    fn perform_subcompose<Content>(&mut self, slot_id: SlotId, content: Content) -> Vec<NodeId>
183    where
184        Content: FnOnce(),
185    {
186        let mut inner = self.parent_handle.inner.borrow_mut();
187
188        // Reuse or create virtual node
189        let (virtual_node_id, is_reused) =
190            if let Some(node_id) = self.state.take_node_from_reusables(slot_id) {
191                (node_id, true)
192            } else {
193                let id = allocate_virtual_node_id();
194                let node = LayoutNode::new_virtual();
195                // CRITICAL FIX: Register virtual node in Applier so that insert_child commands
196                // can find it. Previously, virtual nodes were only stored in inner.virtual_nodes
197                // which caused applier.get_mut(virtual_node_id) to fail, breaking child attachment.
198                if let Err(e) = self
199                    .composer
200                    .register_virtual_node(id, Box::new(node.clone()))
201                {
202                    eprintln!(
203                        "[Subcompose] Failed to register virtual node {}: {:?}",
204                        id, e
205                    );
206                }
207                register_layout_node(id, &node);
208
209                inner.virtual_nodes.insert(id, Rc::new(node));
210                inner.children.insert(id);
211                (id, false)
212            };
213
214        // Manually link parent
215        if let Some(v_node) = inner.virtual_nodes.get(&virtual_node_id) {
216            v_node.set_parent(self.root_id);
217        }
218
219        drop(inner);
220
221        // CRITICAL FIX: Clear children of reused virtual nodes BEFORE subcomposing new content.
222        // Without this, old children remain attached when the node is reused for different items,
223        // causing items from different scroll positions to interleave (e.g., [1,16,31,2,17,32...]).
224        if is_reused {
225            self.composer.clear_node_children(virtual_node_id);
226        }
227
228        let slot_host = self.state.get_or_create_slots(slot_id);
229        let _ = self
230            .composer
231            .subcompose_slot(&slot_host, Some(virtual_node_id), |_| content());
232
233        self.state.register_active(slot_id, &[virtual_node_id], &[]);
234
235        // CRITICAL FIX: Read children from the Applier's copy of the virtual node,
236        // NOT from inner.virtual_nodes. The Applier's copy received insert_child calls
237        // during subcomposition, while inner.virtual_nodes is an out-of-sync clone.
238        self.composer.get_node_children(virtual_node_id)
239    }
240}
241
242impl<'a> SubcomposeLayoutScope for SubcomposeMeasureScopeImpl<'a> {
243    fn constraints(&self) -> Constraints {
244        self.constraints
245    }
246}
247
248impl<'a> SubcomposeMeasureScope for SubcomposeMeasureScopeImpl<'a> {
249    fn subcompose<Content>(&mut self, slot_id: SlotId, content: Content) -> Vec<SubcomposeChild>
250    where
251        Content: FnOnce(),
252    {
253        let nodes = self.perform_subcompose(slot_id, content);
254        nodes.into_iter().map(SubcomposeChild::new).collect()
255    }
256
257    fn measure(&mut self, child: SubcomposeChild, constraints: Constraints) -> SubcomposePlaceable {
258        if self.error.borrow().is_some() {
259            // Already in error state - return zero-size placeable
260            return SubcomposePlaceable::new(child.node_id, Size::default());
261        }
262
263        if let Err(err) = self.composer.apply_pending_commands() {
264            self.record_error(err);
265            return SubcomposePlaceable::new(child.node_id, Size::default());
266        }
267
268        let size = (self.measurer)(child.node_id, constraints);
269        SubcomposePlaceable::new(child.node_id, size)
270    }
271
272    fn node_has_no_parent(&self, node_id: NodeId) -> bool {
273        self.composer.node_has_no_parent(node_id)
274    }
275}
276
277impl<'a> SubcomposeMeasureScopeImpl<'a> {
278    /// Subcomposes content and assigns estimated sizes to children.
279    ///
280    /// This is used by lazy layouts where true measurement happens later.
281    /// The `estimate_size` function provides size estimates based on index.
282    pub fn subcompose_with_size<Content, F>(
283        &mut self,
284        slot_id: SlotId,
285        content: Content,
286        estimate_size: F,
287    ) -> Vec<SubcomposeChild>
288    where
289        Content: FnOnce(),
290        F: Fn(usize) -> Size,
291    {
292        let nodes = self.perform_subcompose(slot_id, content);
293        nodes
294            .into_iter()
295            .enumerate()
296            .map(|(i, node_id)| SubcomposeChild::with_size(node_id, estimate_size(i)))
297            .collect()
298    }
299
300    /// Returns the number of active slots in the subcompose state.
301    ///
302    /// Used by lazy layouts to report statistics about slot usage.
303    pub fn active_slots_count(&self) -> usize {
304        self.state.active_slots_count()
305    }
306
307    /// Returns the number of reusable slots in the pool.
308    ///
309    /// Used by lazy layouts to report statistics about cached slots.
310    pub fn reusable_slots_count(&self) -> usize {
311        self.state.reusable_slots_count()
312    }
313
314    /// Registers the content type for a slot.
315    ///
316    /// Call this before `subcompose()` to enable content-type-aware slot reuse.
317    /// If the policy supports content types (like `ContentTypeReusePolicy`),
318    /// slots with matching content types can reuse each other's nodes.
319    pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
320        self.state.register_content_type(slot_id, content_type);
321    }
322
323    /// Updates the content type for a slot, handling Some→None transitions.
324    ///
325    /// If `content_type` is `Some(type)`, registers the type for the slot.
326    /// If `content_type` is `None`, removes any previously registered type.
327    /// This ensures stale types don't drive incorrect reuse after transitions.
328    pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
329        self.state.update_content_type(slot_id, content_type);
330    }
331
332    /// Returns whether the last subcomposed slot was reused.
333    ///
334    /// Returns `Some(true)` if the slot already existed (was reused from pool or
335    /// was recomposed), `Some(false)` if it was newly created, or `None` if no
336    /// slot has been subcomposed yet this pass.
337    ///
338    /// This is useful for tracking composition statistics in lazy layouts.
339    pub fn was_last_slot_reused(&self) -> Option<bool> {
340        self.state.was_last_slot_reused()
341    }
342}
343
344/// Trait object representing a reusable measure policy.
345pub type MeasurePolicy =
346    dyn for<'scope> Fn(&mut SubcomposeMeasureScopeImpl<'scope>, Constraints) -> MeasureResult;
347
348/// Node responsible for orchestrating measure-time subcomposition.
349pub struct SubcomposeLayoutNode {
350    inner: Rc<RefCell<SubcomposeLayoutNodeInner>>,
351    /// Parent tracking for dirty flag bubbling (P0.2 fix)
352    parent: Cell<Option<NodeId>>,
353    /// Node's own ID
354    id: Cell<Option<NodeId>>,
355    // Dirty flags for selective measure/layout/render
356    needs_measure: Cell<bool>,
357    needs_layout: Cell<bool>,
358    needs_semantics: Cell<bool>,
359    needs_redraw: Cell<bool>,
360    needs_pointer_pass: Cell<bool>,
361    needs_focus_sync: Cell<bool>,
362    virtual_children_count: Cell<usize>,
363}
364
365impl SubcomposeLayoutNode {
366    pub fn new(modifier: Modifier, measure_policy: Rc<MeasurePolicy>) -> Self {
367        let inner = Rc::new(RefCell::new(SubcomposeLayoutNodeInner::new(measure_policy)));
368        let node = Self {
369            inner,
370            parent: Cell::new(None),
371            id: Cell::new(None),
372            needs_measure: Cell::new(true),
373            needs_layout: Cell::new(true),
374            needs_semantics: Cell::new(true),
375            needs_redraw: Cell::new(true),
376            needs_pointer_pass: Cell::new(false),
377            needs_focus_sync: Cell::new(false),
378            virtual_children_count: Cell::new(0),
379        };
380        // Set modifier and dispatch invalidations after borrow is released
381        // Pass empty prev_caps since this is initial construction
382        let (invalidations, _) = node.inner.borrow_mut().set_modifier_collect(modifier);
383        node.dispatch_modifier_invalidations(&invalidations, NodeCapabilities::empty());
384        node
385    }
386
387    /// Creates a SubcomposeLayoutNode with ContentTypeReusePolicy.
388    ///
389    /// Use this for lazy lists to enable content-type-aware slot reuse.
390    /// Slots with matching content types can reuse each other's nodes,
391    /// improving efficiency when scrolling through items with different types.
392    pub fn with_content_type_policy(modifier: Modifier, measure_policy: Rc<MeasurePolicy>) -> Self {
393        let mut inner_data = SubcomposeLayoutNodeInner::new(measure_policy);
394        inner_data
395            .state
396            .set_policy(Box::new(cranpose_core::ContentTypeReusePolicy::new()));
397        let inner = Rc::new(RefCell::new(inner_data));
398        let node = Self {
399            inner,
400            parent: Cell::new(None),
401            id: Cell::new(None),
402            needs_measure: Cell::new(true),
403            needs_layout: Cell::new(true),
404            needs_semantics: Cell::new(true),
405            needs_redraw: Cell::new(true),
406            needs_pointer_pass: Cell::new(false),
407            needs_focus_sync: Cell::new(false),
408            virtual_children_count: Cell::new(0),
409        };
410        // Set modifier and dispatch invalidations after borrow is released
411        // Pass empty prev_caps since this is initial construction
412        let (invalidations, _) = node.inner.borrow_mut().set_modifier_collect(modifier);
413        node.dispatch_modifier_invalidations(&invalidations, NodeCapabilities::empty());
414        node
415    }
416
417    pub fn handle(&self) -> SubcomposeLayoutNodeHandle {
418        SubcomposeLayoutNodeHandle {
419            inner: Rc::clone(&self.inner),
420        }
421    }
422
423    pub fn set_measure_policy(&mut self, policy: Rc<MeasurePolicy>) {
424        self.inner.borrow_mut().set_measure_policy(policy);
425    }
426
427    pub fn set_modifier(&mut self, modifier: Modifier) {
428        // Capture capabilities BEFORE updating to detect removed modifiers
429        let prev_caps = self.modifier_capabilities();
430        // Collect invalidations while inner is borrowed, then dispatch after release
431        let (invalidations, modifier_changed) = {
432            let mut inner = self.inner.borrow_mut();
433            inner.set_modifier_collect(modifier)
434        };
435        // Now dispatch invalidations after the borrow is released
436        // Pass both prev and curr caps so removed modifiers still trigger invalidation
437        self.dispatch_modifier_invalidations(&invalidations, prev_caps);
438        if modifier_changed {
439            self.mark_needs_measure();
440            self.request_semantics_update();
441        }
442    }
443
444    pub fn set_debug_modifiers(&mut self, enabled: bool) {
445        self.inner.borrow_mut().set_debug_modifiers(enabled);
446    }
447
448    pub fn modifier(&self) -> Modifier {
449        self.handle().modifier()
450    }
451
452    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
453        self.inner.borrow().resolved_modifiers
454    }
455
456    pub fn state(&self) -> Ref<'_, SubcomposeState> {
457        Ref::map(self.inner.borrow(), |inner| &inner.state)
458    }
459
460    pub fn state_mut(&self) -> RefMut<'_, SubcomposeState> {
461        RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.state)
462    }
463
464    pub fn active_children(&self) -> Vec<NodeId> {
465        self.inner.borrow().children.iter().copied().collect()
466    }
467
468    /// Mark this node as needing measure. Also marks it as needing layout.
469    pub fn mark_needs_measure(&self) {
470        self.needs_measure.set(true);
471        self.needs_layout.set(true);
472    }
473
474    /// Mark this node as needing layout (but not necessarily measure).
475    pub fn mark_needs_layout_flag(&self) {
476        self.needs_layout.set(true);
477    }
478
479    /// Mark this node as needing redraw without forcing measure/layout.
480    pub fn mark_needs_redraw(&self) {
481        self.needs_redraw.set(true);
482        if let Some(id) = self.id.get() {
483            crate::schedule_draw_repass(id);
484        }
485        crate::request_render_invalidation();
486    }
487
488    /// Check if this node needs measure.
489    pub fn needs_measure(&self) -> bool {
490        self.needs_measure.get()
491    }
492
493    /// Mark this node as needing semantics recomputation.
494    pub fn mark_needs_semantics(&self) {
495        self.needs_semantics.set(true);
496    }
497
498    /// Returns true when semantics need to be recomputed.
499    pub fn needs_semantics_flag(&self) -> bool {
500        self.needs_semantics.get()
501    }
502
503    /// Returns true when this node requested a redraw since the last render pass.
504    pub fn needs_redraw(&self) -> bool {
505        self.needs_redraw.get()
506    }
507
508    pub fn clear_needs_redraw(&self) {
509        self.needs_redraw.set(false);
510    }
511
512    /// Marks this node as needing a fresh pointer-input pass.
513    pub fn mark_needs_pointer_pass(&self) {
514        self.needs_pointer_pass.set(true);
515    }
516
517    /// Returns true when pointer-input state needs to be recomputed.
518    pub fn needs_pointer_pass(&self) -> bool {
519        self.needs_pointer_pass.get()
520    }
521
522    /// Clears the pointer-input dirty flag after hosts service it.
523    pub fn clear_needs_pointer_pass(&self) {
524        self.needs_pointer_pass.set(false);
525    }
526
527    /// Marks this node as needing a focus synchronization.
528    pub fn mark_needs_focus_sync(&self) {
529        self.needs_focus_sync.set(true);
530    }
531
532    /// Returns true when focus state needs to be synchronized.
533    pub fn needs_focus_sync(&self) -> bool {
534        self.needs_focus_sync.get()
535    }
536
537    /// Clears the focus dirty flag after the focus manager processes it.
538    pub fn clear_needs_focus_sync(&self) {
539        self.needs_focus_sync.set(false);
540    }
541
542    fn request_semantics_update(&self) {
543        let already_dirty = self.needs_semantics.replace(true);
544        if already_dirty {
545            return;
546        }
547
548        if let Some(id) = self.id.get() {
549            cranpose_core::queue_semantics_invalidation(id);
550        }
551    }
552
553    /// Returns the modifier capabilities for this node.
554    pub fn modifier_capabilities(&self) -> NodeCapabilities {
555        self.inner.borrow().modifier_capabilities
556    }
557
558    pub fn has_layout_modifier_nodes(&self) -> bool {
559        self.modifier_capabilities()
560            .contains(NodeCapabilities::LAYOUT)
561    }
562
563    pub fn has_draw_modifier_nodes(&self) -> bool {
564        self.modifier_capabilities()
565            .contains(NodeCapabilities::DRAW)
566    }
567
568    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
569        self.modifier_capabilities()
570            .contains(NodeCapabilities::POINTER_INPUT)
571    }
572
573    pub fn has_semantics_modifier_nodes(&self) -> bool {
574        self.modifier_capabilities()
575            .contains(NodeCapabilities::SEMANTICS)
576    }
577
578    pub fn has_focus_modifier_nodes(&self) -> bool {
579        self.modifier_capabilities()
580            .contains(NodeCapabilities::FOCUS)
581    }
582
583    /// Dispatches modifier invalidations to the appropriate subsystems.
584    ///
585    /// `prev_caps` contains the capabilities BEFORE the modifier update.
586    /// Invalidations are dispatched if EITHER the previous OR current capabilities
587    /// include the relevant type. This ensures that removing the last modifier
588    /// of a type still triggers proper invalidation.
589    fn dispatch_modifier_invalidations(
590        &self,
591        invalidations: &[ModifierInvalidation],
592        prev_caps: NodeCapabilities,
593    ) {
594        let curr_caps = self.modifier_capabilities();
595        for invalidation in invalidations {
596            match invalidation.kind() {
597                InvalidationKind::Layout => {
598                    if curr_caps.contains(NodeCapabilities::LAYOUT)
599                        || prev_caps.contains(NodeCapabilities::LAYOUT)
600                    {
601                        self.mark_needs_measure();
602                    }
603                }
604                InvalidationKind::Draw => {
605                    if curr_caps.contains(NodeCapabilities::DRAW)
606                        || prev_caps.contains(NodeCapabilities::DRAW)
607                    {
608                        self.mark_needs_redraw();
609                    }
610                }
611                InvalidationKind::PointerInput => {
612                    if curr_caps.contains(NodeCapabilities::POINTER_INPUT)
613                        || prev_caps.contains(NodeCapabilities::POINTER_INPUT)
614                    {
615                        self.mark_needs_pointer_pass();
616                        crate::request_pointer_invalidation();
617                        // Schedule pointer repass for this node
618                        if let Some(id) = self.id.get() {
619                            crate::schedule_pointer_repass(id);
620                        }
621                    }
622                }
623                InvalidationKind::Semantics => {
624                    self.request_semantics_update();
625                }
626                InvalidationKind::Focus => {
627                    if curr_caps.contains(NodeCapabilities::FOCUS)
628                        || prev_caps.contains(NodeCapabilities::FOCUS)
629                    {
630                        self.mark_needs_focus_sync();
631                        crate::request_focus_invalidation();
632                        // Schedule focus invalidation for this node
633                        if let Some(id) = self.id.get() {
634                            crate::schedule_focus_invalidation(id);
635                        }
636                    }
637                }
638            }
639        }
640    }
641}
642
643impl cranpose_core::Node for SubcomposeLayoutNode {
644    fn mount(&mut self) {
645        let mut inner = self.inner.borrow_mut();
646        let (chain, mut context) = inner.modifier_chain.chain_and_context_mut();
647        chain.repair_chain();
648        chain.attach_nodes(&mut *context);
649    }
650
651    fn unmount(&mut self) {
652        self.inner
653            .borrow_mut()
654            .modifier_chain
655            .chain_mut()
656            .detach_nodes();
657    }
658
659    fn insert_child(&mut self, child: NodeId) {
660        if is_virtual_node(child) {
661            let count = self.virtual_children_count.get();
662            self.virtual_children_count.set(count + 1);
663        }
664        self.inner.borrow_mut().children.insert(child);
665    }
666
667    fn remove_child(&mut self, child: NodeId) {
668        if self.inner.borrow_mut().children.shift_remove(&child) && is_virtual_node(child) {
669            let count = self.virtual_children_count.get();
670            if count > 0 {
671                self.virtual_children_count.set(count - 1);
672            }
673        }
674    }
675
676    fn move_child(&mut self, from: usize, to: usize) {
677        let mut inner = self.inner.borrow_mut();
678        if from == to || from >= inner.children.len() {
679            return;
680        }
681        let mut ordered: Vec<NodeId> = inner.children.iter().copied().collect();
682        let child = ordered.remove(from);
683        let target = to.min(ordered.len());
684        ordered.insert(target, child);
685        inner.children.clear();
686        for id in ordered {
687            inner.children.insert(id);
688        }
689    }
690
691    fn update_children(&mut self, children: &[NodeId]) {
692        let mut inner = self.inner.borrow_mut();
693        inner.children.clear();
694        for &child in children {
695            inner.children.insert(child);
696        }
697    }
698
699    fn children(&self) -> Vec<NodeId> {
700        let inner = self.inner.borrow();
701        // Return placement children if available (they represent the actually rendered nodes)
702        // Otherwise fall back to structural children
703        if !inner.last_placements.is_empty() {
704            inner.last_placements.clone()
705        } else {
706            inner.children.iter().copied().collect()
707        }
708    }
709
710    fn set_node_id(&mut self, id: NodeId) {
711        self.id.set(Some(id));
712        self.inner.borrow_mut().modifier_chain.set_node_id(Some(id));
713    }
714
715    fn on_attached_to_parent(&mut self, parent: NodeId) {
716        self.parent.set(Some(parent));
717    }
718
719    fn on_removed_from_parent(&mut self) {
720        self.parent.set(None);
721    }
722
723    fn parent(&self) -> Option<NodeId> {
724        self.parent.get()
725    }
726
727    fn mark_needs_layout(&self) {
728        self.needs_layout.set(true);
729    }
730
731    fn needs_layout(&self) -> bool {
732        self.needs_layout.get()
733    }
734
735    fn mark_needs_measure(&self) {
736        self.needs_measure.set(true);
737        self.needs_layout.set(true); // Measure implies layout
738    }
739
740    fn needs_measure(&self) -> bool {
741        self.needs_measure.get()
742    }
743
744    fn mark_needs_semantics(&self) {
745        self.needs_semantics.set(true);
746    }
747
748    fn needs_semantics(&self) -> bool {
749        self.needs_semantics.get()
750    }
751
752    /// Minimal parent setter for dirty flag bubbling.
753    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
754        self.parent.set(Some(parent));
755    }
756}
757
758#[derive(Clone)]
759pub struct SubcomposeLayoutNodeHandle {
760    inner: Rc<RefCell<SubcomposeLayoutNodeInner>>,
761}
762
763impl SubcomposeLayoutNodeHandle {
764    pub fn modifier(&self) -> Modifier {
765        self.inner.borrow().modifier.clone()
766    }
767
768    pub fn layout_properties(&self) -> crate::modifier::LayoutProperties {
769        self.resolved_modifiers().layout_properties()
770    }
771
772    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
773        self.inner.borrow().resolved_modifiers
774    }
775
776    pub fn total_offset(&self) -> Point {
777        self.resolved_modifiers().offset()
778    }
779
780    pub fn modifier_capabilities(&self) -> NodeCapabilities {
781        self.inner.borrow().modifier_capabilities
782    }
783
784    pub fn has_layout_modifier_nodes(&self) -> bool {
785        self.modifier_capabilities()
786            .contains(NodeCapabilities::LAYOUT)
787    }
788
789    pub fn has_draw_modifier_nodes(&self) -> bool {
790        self.modifier_capabilities()
791            .contains(NodeCapabilities::DRAW)
792    }
793
794    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
795        self.modifier_capabilities()
796            .contains(NodeCapabilities::POINTER_INPUT)
797    }
798
799    pub fn has_semantics_modifier_nodes(&self) -> bool {
800        self.modifier_capabilities()
801            .contains(NodeCapabilities::SEMANTICS)
802    }
803
804    pub fn has_focus_modifier_nodes(&self) -> bool {
805        self.modifier_capabilities()
806            .contains(NodeCapabilities::FOCUS)
807    }
808
809    pub fn set_debug_modifiers(&self, enabled: bool) {
810        self.inner.borrow_mut().set_debug_modifiers(enabled);
811    }
812
813    pub fn measure<'a>(
814        &self,
815        composer: &Composer,
816        node_id: NodeId,
817        constraints: Constraints,
818        measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
819        error: Rc<RefCell<Option<NodeError>>>,
820    ) -> Result<MeasureResult, NodeError> {
821        let (policy, mut state, slots) = {
822            let mut inner = self.inner.borrow_mut();
823            let policy = Rc::clone(&inner.measure_policy);
824            let state = std::mem::take(&mut inner.state);
825            let slots = std::mem::take(&mut inner.slots);
826            (policy, state, slots)
827        };
828        state.begin_pass();
829
830        let previous = composer.phase();
831        if !matches!(previous, Phase::Measure | Phase::Layout) {
832            composer.enter_phase(Phase::Measure);
833        }
834
835        let slots_host = Rc::new(SlotsHost::new(slots));
836        let constraints_copy = constraints;
837        // Architecture Note: Using subcompose_slot (not subcompose_in) to preserve the
838        // SlotTable across measurement passes. This matches JC's SubcomposeLayout behavior
839        // where `subcompose()` is called during measure and the slot table persists between
840        // frames. Without this, lazy list item groups would be wiped and recreated on every
841        // scroll frame, causing O(visible_items) recomposition overhead ("thrashing").
842        //
843        // Reference: LazyLayoutMeasureScope.subcompose() in JC reuses existing slots by key,
844        // and SubcomposeLayoutState holds `slotIdToNode` map across measurements.
845        let result = composer.subcompose_slot(&slots_host, Some(node_id), |inner_composer| {
846            let mut scope = SubcomposeMeasureScopeImpl::new(
847                inner_composer.clone(),
848                &mut state,
849                constraints_copy,
850                measurer,
851                Rc::clone(&error),
852                self.clone(), // Pass handle
853                node_id,      // Pass root_id
854            );
855            (policy)(&mut scope, constraints_copy)
856        })?;
857
858        state.finish_pass();
859
860        if previous != composer.phase() {
861            composer.enter_phase(previous);
862        }
863
864        {
865            let mut inner = self.inner.borrow_mut();
866            inner.slots = slots_host.take();
867            inner.state = state;
868
869            // Store placement children for children() traversal.
870            // This avoids clearing/rebuilding the structural children set on every measure,
871            // eliminating O(n) allocator churn. The structural children (virtual nodes) are
872            // tracked via insert_child/remove_child, while last_placements tracks rendered nodes.
873            inner.last_placements = result.placements.iter().map(|p| p.node_id).collect();
874        }
875
876        Ok(result)
877    }
878
879    pub fn set_active_children<I>(&self, children: I)
880    where
881        I: IntoIterator<Item = NodeId>,
882    {
883        let mut inner = self.inner.borrow_mut();
884        inner.children.clear();
885        for child in children {
886            inner.children.insert(child);
887        }
888    }
889}
890
891struct SubcomposeLayoutNodeInner {
892    modifier: Modifier,
893    modifier_chain: ModifierChainHandle,
894    resolved_modifiers: ResolvedModifiers,
895    modifier_capabilities: NodeCapabilities,
896    state: SubcomposeState,
897    measure_policy: Rc<MeasurePolicy>,
898    children: IndexSet<NodeId>,
899    slots: SlotBackend,
900    debug_modifiers: bool,
901    // Owns virtual nodes created during subcomposition
902    virtual_nodes: HashMap<NodeId, Rc<LayoutNode>>,
903    // Cached placement children from the last measure pass.
904    // Used by children() for semantic/render traversal without clearing structural children.
905    last_placements: Vec<NodeId>,
906}
907
908impl SubcomposeLayoutNodeInner {
909    fn new(measure_policy: Rc<MeasurePolicy>) -> Self {
910        Self {
911            modifier: Modifier::empty(),
912            modifier_chain: ModifierChainHandle::new(),
913            resolved_modifiers: ResolvedModifiers::default(),
914            modifier_capabilities: NodeCapabilities::default(),
915            state: SubcomposeState::default(),
916            measure_policy,
917            children: IndexSet::new(),
918            slots: SlotBackend::default(),
919            debug_modifiers: false,
920            virtual_nodes: HashMap::new(),
921            last_placements: Vec::new(),
922        }
923    }
924
925    fn set_measure_policy(&mut self, policy: Rc<MeasurePolicy>) {
926        self.measure_policy = policy;
927    }
928
929    /// Updates the modifier and collects invalidations without dispatching them.
930    /// Returns the invalidations and whether the modifier changed.
931    fn set_modifier_collect(&mut self, modifier: Modifier) -> (Vec<ModifierInvalidation>, bool) {
932        let modifier_changed = !self.modifier.structural_eq(&modifier);
933        self.modifier = modifier;
934        self.modifier_chain.set_debug_logging(self.debug_modifiers);
935        let modifier_local_invalidations = self.modifier_chain.update(&self.modifier);
936        self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
937        self.modifier_capabilities = self.modifier_chain.capabilities();
938
939        // Collect invalidations from modifier chain updates
940        let mut invalidations = self.modifier_chain.take_invalidations();
941        invalidations.extend(modifier_local_invalidations);
942
943        (invalidations, modifier_changed)
944    }
945
946    fn set_debug_modifiers(&mut self, enabled: bool) {
947        self.debug_modifiers = enabled;
948        self.modifier_chain.set_debug_logging(enabled);
949    }
950}
951
952#[cfg(test)]
953#[path = "tests/subcompose_layout_tests.rs"]
954mod tests;