Skip to main content

cranpose_ui/
subcompose_layout.rs

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