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