Skip to main content

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