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