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