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.modifier_slices_snapshot.borrow().clone()
510    }
511
512    pub fn state(&self) -> Ref<'_, SubcomposeState> {
513        Ref::map(self.inner.borrow(), |inner| &inner.state)
514    }
515
516    pub fn state_mut(&self) -> RefMut<'_, SubcomposeState> {
517        RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.state)
518    }
519
520    pub fn active_children(&self) -> Vec<NodeId> {
521        self.inner.borrow().children.clone()
522    }
523
524    /// Mark this node as needing measure. Also marks it as needing layout.
525    pub fn mark_needs_measure(&self) {
526        self.needs_measure.set(true);
527        self.needs_layout.set(true);
528    }
529
530    /// Mark this node as needing layout (but not necessarily measure).
531    pub fn mark_needs_layout_flag(&self) {
532        self.needs_layout.set(true);
533    }
534
535    /// Mark this node as needing redraw without forcing measure/layout.
536    pub fn mark_needs_redraw(&self) {
537        self.needs_redraw.set(true);
538        if let Some(id) = self.id.get() {
539            crate::schedule_draw_repass(id);
540        }
541        crate::request_render_invalidation();
542    }
543
544    /// Check if this node needs measure.
545    pub fn needs_measure(&self) -> bool {
546        self.needs_measure.get()
547    }
548
549    /// Mark this node as needing semantics recomputation.
550    pub fn mark_needs_semantics(&self) {
551        self.needs_semantics.set(true);
552    }
553
554    /// Returns true when semantics need to be recomputed.
555    pub fn needs_semantics_flag(&self) -> bool {
556        self.needs_semantics.get()
557    }
558
559    /// Returns true when this node requested a redraw since the last render pass.
560    pub fn needs_redraw(&self) -> bool {
561        self.needs_redraw.get()
562    }
563
564    pub fn clear_needs_redraw(&self) {
565        self.needs_redraw.set(false);
566    }
567
568    /// Marks this node as needing a fresh pointer-input pass.
569    pub fn mark_needs_pointer_pass(&self) {
570        self.needs_pointer_pass.set(true);
571    }
572
573    /// Returns true when pointer-input state needs to be recomputed.
574    pub fn needs_pointer_pass(&self) -> bool {
575        self.needs_pointer_pass.get()
576    }
577
578    /// Clears the pointer-input dirty flag after hosts service it.
579    pub fn clear_needs_pointer_pass(&self) {
580        self.needs_pointer_pass.set(false);
581    }
582
583    /// Marks this node as needing a focus synchronization.
584    pub fn mark_needs_focus_sync(&self) {
585        self.needs_focus_sync.set(true);
586    }
587
588    /// Returns true when focus state needs to be synchronized.
589    pub fn needs_focus_sync(&self) -> bool {
590        self.needs_focus_sync.get()
591    }
592
593    /// Clears the focus dirty flag after the focus manager processes it.
594    pub fn clear_needs_focus_sync(&self) {
595        self.needs_focus_sync.set(false);
596    }
597
598    fn request_semantics_update(&self) {
599        let already_dirty = self.needs_semantics.replace(true);
600        if already_dirty {
601            return;
602        }
603
604        if let Some(id) = self.id.get() {
605            cranpose_core::queue_semantics_invalidation(id);
606        }
607    }
608
609    /// Returns the modifier capabilities for this node.
610    pub fn modifier_capabilities(&self) -> NodeCapabilities {
611        self.inner.borrow().modifier_capabilities
612    }
613
614    pub fn has_layout_modifier_nodes(&self) -> bool {
615        self.modifier_capabilities()
616            .contains(NodeCapabilities::LAYOUT)
617    }
618
619    pub fn has_draw_modifier_nodes(&self) -> bool {
620        self.modifier_capabilities()
621            .contains(NodeCapabilities::DRAW)
622    }
623
624    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
625        self.modifier_capabilities()
626            .contains(NodeCapabilities::POINTER_INPUT)
627    }
628
629    pub fn has_semantics_modifier_nodes(&self) -> bool {
630        self.modifier_capabilities()
631            .contains(NodeCapabilities::SEMANTICS)
632    }
633
634    pub fn has_focus_modifier_nodes(&self) -> bool {
635        self.modifier_capabilities()
636            .contains(NodeCapabilities::FOCUS)
637    }
638
639    /// Dispatches modifier invalidations to the appropriate subsystems.
640    ///
641    /// `prev_caps` contains the capabilities BEFORE the modifier update.
642    /// Invalidations are dispatched if EITHER the previous OR current capabilities
643    /// include the relevant type. This ensures that removing the last modifier
644    /// of a type still triggers proper invalidation.
645    fn dispatch_modifier_invalidations(
646        &self,
647        invalidations: &[ModifierInvalidation],
648        prev_caps: NodeCapabilities,
649    ) {
650        let curr_caps = self.modifier_capabilities();
651        for invalidation in invalidations {
652            match invalidation.kind() {
653                InvalidationKind::Layout => {
654                    if curr_caps.contains(NodeCapabilities::LAYOUT)
655                        || prev_caps.contains(NodeCapabilities::LAYOUT)
656                    {
657                        self.mark_needs_measure();
658                    }
659                }
660                InvalidationKind::Draw => {
661                    if curr_caps.contains(NodeCapabilities::DRAW)
662                        || prev_caps.contains(NodeCapabilities::DRAW)
663                    {
664                        self.mark_needs_redraw();
665                    }
666                }
667                InvalidationKind::PointerInput => {
668                    if curr_caps.contains(NodeCapabilities::POINTER_INPUT)
669                        || prev_caps.contains(NodeCapabilities::POINTER_INPUT)
670                    {
671                        self.mark_needs_pointer_pass();
672                        crate::request_pointer_invalidation();
673                        // Schedule pointer repass for this node
674                        if let Some(id) = self.id.get() {
675                            crate::schedule_pointer_repass(id);
676                        }
677                    }
678                }
679                InvalidationKind::Semantics => {
680                    self.request_semantics_update();
681                }
682                InvalidationKind::Focus => {
683                    if curr_caps.contains(NodeCapabilities::FOCUS)
684                        || prev_caps.contains(NodeCapabilities::FOCUS)
685                    {
686                        self.mark_needs_focus_sync();
687                        crate::request_focus_invalidation();
688                        // Schedule focus invalidation for this node
689                        if let Some(id) = self.id.get() {
690                            crate::schedule_focus_invalidation(id);
691                        }
692                    }
693                }
694            }
695        }
696    }
697}
698
699impl cranpose_core::Node for SubcomposeLayoutNode {
700    fn mount(&mut self) {
701        let mut inner = self.inner.borrow_mut();
702        let (chain, mut context) = inner.modifier_chain.chain_and_context_mut();
703        chain.repair_chain();
704        chain.attach_nodes(&mut *context);
705    }
706
707    fn unmount(&mut self) {
708        self.inner
709            .borrow_mut()
710            .modifier_chain
711            .chain_mut()
712            .detach_nodes();
713    }
714
715    fn insert_child(&mut self, child: NodeId) {
716        let mut inner = self.inner.borrow_mut();
717        if inner.children.contains(&child) {
718            return;
719        }
720        if is_virtual_node(child) {
721            let count = self.virtual_children_count.get();
722            self.virtual_children_count.set(count + 1);
723        }
724        inner.children.push(child);
725    }
726
727    fn remove_child(&mut self, child: NodeId) {
728        let mut inner = self.inner.borrow_mut();
729        let before = inner.children.len();
730        inner.children.retain(|&id| id != child);
731        if inner.children.len() < before && is_virtual_node(child) {
732            let count = self.virtual_children_count.get();
733            if count > 0 {
734                self.virtual_children_count.set(count - 1);
735            }
736        }
737    }
738
739    fn move_child(&mut self, from: usize, to: usize) {
740        let mut inner = self.inner.borrow_mut();
741        if from == to || from >= inner.children.len() {
742            return;
743        }
744        let child = inner.children.remove(from);
745        let target = to.min(inner.children.len());
746        inner.children.insert(target, child);
747    }
748
749    fn update_children(&mut self, children: &[NodeId]) {
750        let mut inner = self.inner.borrow_mut();
751        inner.children.clear();
752        inner.children.extend_from_slice(children);
753    }
754
755    fn children(&self) -> Vec<NodeId> {
756        let inner = self.inner.borrow();
757        if !inner.last_placements.is_empty() {
758            inner.last_placements.clone()
759        } else {
760            inner.children.clone()
761        }
762    }
763
764    fn set_node_id(&mut self, id: NodeId) {
765        self.id.set(Some(id));
766        self.inner.borrow_mut().modifier_chain.set_node_id(Some(id));
767    }
768
769    fn on_attached_to_parent(&mut self, parent: NodeId) {
770        self.parent.set(Some(parent));
771    }
772
773    fn on_removed_from_parent(&mut self) {
774        self.parent.set(None);
775    }
776
777    fn parent(&self) -> Option<NodeId> {
778        self.parent.get()
779    }
780
781    fn mark_needs_layout(&self) {
782        self.needs_layout.set(true);
783    }
784
785    fn needs_layout(&self) -> bool {
786        self.needs_layout.get()
787    }
788
789    fn mark_needs_measure(&self) {
790        self.needs_measure.set(true);
791        self.needs_layout.set(true); // Measure implies layout
792    }
793
794    fn needs_measure(&self) -> bool {
795        self.needs_measure.get()
796    }
797
798    fn mark_needs_semantics(&self) {
799        self.needs_semantics.set(true);
800    }
801
802    fn needs_semantics(&self) -> bool {
803        self.needs_semantics.get()
804    }
805
806    /// Minimal parent setter for dirty flag bubbling.
807    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
808        self.parent.set(Some(parent));
809    }
810}
811
812#[derive(Clone)]
813pub struct SubcomposeLayoutNodeHandle {
814    inner: Rc<RefCell<SubcomposeLayoutNodeInner>>,
815}
816
817impl SubcomposeLayoutNodeHandle {
818    pub fn modifier(&self) -> Modifier {
819        self.inner.borrow().modifier.clone()
820    }
821
822    pub fn layout_properties(&self) -> crate::modifier::LayoutProperties {
823        self.resolved_modifiers().layout_properties()
824    }
825
826    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
827        self.inner.borrow().resolved_modifiers
828    }
829
830    pub fn total_offset(&self) -> Point {
831        self.resolved_modifiers().offset()
832    }
833
834    pub fn modifier_capabilities(&self) -> NodeCapabilities {
835        self.inner.borrow().modifier_capabilities
836    }
837
838    pub fn has_layout_modifier_nodes(&self) -> bool {
839        self.modifier_capabilities()
840            .contains(NodeCapabilities::LAYOUT)
841    }
842
843    pub fn has_draw_modifier_nodes(&self) -> bool {
844        self.modifier_capabilities()
845            .contains(NodeCapabilities::DRAW)
846    }
847
848    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
849        self.modifier_capabilities()
850            .contains(NodeCapabilities::POINTER_INPUT)
851    }
852
853    pub fn has_semantics_modifier_nodes(&self) -> bool {
854        self.modifier_capabilities()
855            .contains(NodeCapabilities::SEMANTICS)
856    }
857
858    pub fn has_focus_modifier_nodes(&self) -> bool {
859        self.modifier_capabilities()
860            .contains(NodeCapabilities::FOCUS)
861    }
862
863    pub fn set_debug_modifiers(&self, enabled: bool) {
864        self.inner.borrow_mut().set_debug_modifiers(enabled);
865    }
866
867    pub fn measure<'a>(
868        &self,
869        composer: &Composer,
870        node_id: NodeId,
871        constraints: Constraints,
872        measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
873        error: Rc<RefCell<Option<NodeError>>>,
874    ) -> Result<MeasureResult, NodeError> {
875        let (policy, mut state, slots) = {
876            let mut inner = self.inner.borrow_mut();
877            let policy = Rc::clone(&inner.measure_policy);
878            let state = std::mem::take(&mut inner.state);
879            let slots = std::mem::take(&mut inner.slots);
880            (policy, state, slots)
881        };
882        state.begin_pass();
883
884        let previous = composer.phase();
885        if !matches!(previous, Phase::Measure | Phase::Layout) {
886            composer.enter_phase(Phase::Measure);
887        }
888
889        let slots_host = Rc::new(SlotsHost::new(slots));
890        let constraints_copy = constraints;
891        // Architecture Note: Using subcompose_slot (not subcompose_in) to preserve the
892        // SlotTable across measurement passes. This matches JC's SubcomposeLayout behavior
893        // where `subcompose()` is called during measure and the slot table persists between
894        // frames. Without this, lazy list item groups would be wiped and recreated on every
895        // scroll frame, causing O(visible_items) recomposition overhead ("thrashing").
896        //
897        // Reference: LazyLayoutMeasureScope.subcompose() in JC reuses existing slots by key,
898        // and SubcomposeLayoutState holds `slotIdToNode` map across measurements.
899        let (result, _) =
900            composer.subcompose_slot(&slots_host, Some(node_id), |inner_composer| {
901                let mut scope = SubcomposeMeasureScopeImpl::new(
902                    inner_composer.clone(),
903                    &mut state,
904                    constraints_copy,
905                    measurer,
906                    Rc::clone(&error),
907                    self.clone(), // Pass handle
908                    node_id,      // Pass root_id
909                );
910                (policy)(&mut scope, constraints_copy)
911            })?;
912
913        state.finish_pass();
914
915        if previous != composer.phase() {
916            composer.enter_phase(previous);
917        }
918
919        {
920            let mut inner = self.inner.borrow_mut();
921            inner.slots = slots_host.take();
922            inner.state = state;
923
924            // Store placement children for children() traversal.
925            // This avoids clearing/rebuilding the structural children set on every measure,
926            // eliminating O(n) allocator churn. The structural children (virtual nodes) are
927            // tracked via insert_child/remove_child, while last_placements tracks rendered nodes.
928            inner.last_placements = result.placements.iter().map(|p| p.node_id).collect();
929        }
930
931        Ok(result)
932    }
933
934    pub fn set_active_children<I>(&self, children: I)
935    where
936        I: IntoIterator<Item = NodeId>,
937    {
938        let mut inner = self.inner.borrow_mut();
939        inner.children.clear();
940        inner.children.extend(children);
941    }
942}
943
944struct SubcomposeLayoutNodeInner {
945    modifier: Modifier,
946    modifier_chain: ModifierChainHandle,
947    resolved_modifiers: ResolvedModifiers,
948    modifier_capabilities: NodeCapabilities,
949    state: SubcomposeState,
950    measure_policy: Rc<MeasurePolicy>,
951    children: Vec<NodeId>,
952    slots: SlotBackend,
953    debug_modifiers: bool,
954    // Owns virtual nodes created during subcomposition
955    virtual_nodes: HashMap<NodeId, Rc<LayoutNode>>,
956    // Cached placement children from the last measure pass.
957    // Used by children() for semantic/render traversal without clearing structural children.
958    last_placements: Vec<NodeId>,
959}
960
961impl SubcomposeLayoutNodeInner {
962    fn new(measure_policy: Rc<MeasurePolicy>) -> Self {
963        Self {
964            modifier: Modifier::empty(),
965            modifier_chain: ModifierChainHandle::new(),
966            resolved_modifiers: ResolvedModifiers::default(),
967            modifier_capabilities: NodeCapabilities::default(),
968            state: SubcomposeState::default(),
969            measure_policy,
970            children: Vec::new(),
971            slots: SlotBackend::default(),
972            debug_modifiers: false,
973            virtual_nodes: HashMap::new(),
974            last_placements: Vec::new(),
975        }
976    }
977
978    fn set_measure_policy(&mut self, policy: Rc<MeasurePolicy>) {
979        self.measure_policy = policy;
980        // The root measurement subcomposition caches its slot table separately
981        // from per-item slot scopes. When a widget updates the data captured by
982        // the measure lambda through shared cells, the next layout pass must not
983        // reuse the previous root measure group wholesale.
984        self.slots = SlotBackend::default();
985    }
986
987    /// Updates the modifier and collects invalidations without dispatching them.
988    /// Returns the invalidations and whether the modifier changed.
989    fn set_modifier_collect(&mut self, modifier: Modifier) -> (Vec<ModifierInvalidation>, bool) {
990        let modifier_changed = !self.modifier.structural_eq(&modifier);
991        self.modifier = modifier;
992        self.modifier_chain.set_debug_logging(self.debug_modifiers);
993        let modifier_local_invalidations = self.modifier_chain.update(&self.modifier);
994        self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
995        self.modifier_capabilities = self.modifier_chain.capabilities();
996
997        // Collect invalidations from modifier chain updates
998        let mut invalidations = self.modifier_chain.take_invalidations();
999        invalidations.extend(modifier_local_invalidations);
1000
1001        (invalidations, modifier_changed)
1002    }
1003
1004    fn set_debug_modifiers(&mut self, enabled: bool) {
1005        self.debug_modifiers = enabled;
1006        self.modifier_chain.set_debug_logging(enabled);
1007    }
1008}
1009
1010#[cfg(test)]
1011#[path = "tests/subcompose_layout_tests.rs"]
1012mod tests;