Skip to main content

cranpose_ui/
subcompose_layout.rs

1use std::{
2    cell::{Cell, Ref, RefCell, RefMut},
3    collections::HashMap,
4    rc::Rc,
5};
6
7use cranpose_core::{
8    Composer, NodeError, NodeId, Phase, SlotId, SlotTable, SlotsHost, SubcomposeState,
9};
10use cranpose_foundation::{InvalidationKind, ModifierInvalidation, NodeCapabilities};
11pub use cranpose_ui_layout::{Constraints, MeasureResult, Placement};
12use smallvec::SmallVec;
13use web_time::Instant;
14
15use crate::{
16    layout::MeasuredNode,
17    modifier::{
18        Modifier, ModifierChainHandle, ModifierNodeSlices, Point, ResolvedModifiers, Size,
19        collect_modifier_slices_into,
20    },
21    widgets::nodes::{
22        LayoutNode, LayoutNodeCacheHandles, LayoutState, allocate_virtual_node_id, is_virtual_node,
23        register_layout_node,
24    },
25};
26
27fn subcompose_telemetry_enabled() -> bool {
28    cranpose_core::env_flag!("CRANPOSE_SUBCOMPOSE_TELEMETRY")
29}
30
31#[derive(Clone, Copy, Debug)]
32pub struct SubcomposeChild {
33    node_id: NodeId,
34    measured_size: Option<Size>,
35}
36
37impl SubcomposeChild {
38    pub fn new(node_id: NodeId) -> Self {
39        Self {
40            node_id,
41            measured_size: None,
42        }
43    }
44
45    pub fn with_size(node_id: NodeId, size: Size) -> Self {
46        Self {
47            node_id,
48            measured_size: Some(size),
49        }
50    }
51
52    pub fn node_id(&self) -> NodeId {
53        self.node_id
54    }
55
56    pub fn size(&self) -> Size {
57        self.measured_size.unwrap_or(Size {
58            width: 0.0,
59            height: 0.0,
60        })
61    }
62
63    pub fn width(&self) -> f32 {
64        self.size().width
65    }
66
67    pub fn height(&self) -> f32 {
68        self.size().height
69    }
70
71    pub fn set_size(&mut self, size: Size) {
72        self.measured_size = Some(size);
73    }
74}
75
76impl PartialEq for SubcomposeChild {
77    fn eq(&self, other: &Self) -> bool {
78        self.node_id == other.node_id
79    }
80}
81
82pub type SubcomposePlaceable = cranpose_ui_layout::Placeable;
83
84type CachedMeasureBatchRegistrar<'a> =
85    Box<dyn FnMut(&[NodeId], Constraints, &mut Vec<Option<Size>>) + 'a>;
86type RetainedMeasureLookup<'a> = Box<dyn FnMut(NodeId) -> Option<Rc<MeasuredNode>> + 'a>;
87type RetainedMeasureRegistrar<'a> = Box<dyn FnMut(&[Rc<MeasuredNode>]) + 'a>;
88
89pub(crate) struct CachedBatchMeasureInputs<'a> {
90    pub(crate) measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
91    pub(crate) cached_measure_batch_registrar: CachedMeasureBatchRegistrar<'a>,
92    pub(crate) retained_measure_lookup: RetainedMeasureLookup<'a>,
93    pub(crate) retained_measure_registrar: RetainedMeasureRegistrar<'a>,
94    pub(crate) error: &'a RefCell<Option<NodeError>>,
95}
96
97/// Base trait for measurement scopes.
98pub trait SubcomposeLayoutScope: cranpose_ui_layout::MeasureScope {
99    fn constraints(&self) -> Constraints;
100
101    fn layout<I>(&mut self, width: f32, height: f32, placements: I) -> MeasureResult
102    where
103        I: IntoIterator<Item = Placement>,
104    {
105        MeasureResult::new(Size { width, height }, placements.into_iter().collect())
106    }
107}
108
109/// Public trait exposed to measure policies for subcomposition.
110pub trait SubcomposeMeasureScope: SubcomposeLayoutScope {
111    /// Composes `content` into the slot, or reuses the retained composition
112    /// when it is provably current.
113    ///
114    /// `key` must carry every value that flows into `content` from the
115    /// measure policy itself rather than from reactive state — a scaffold's
116    /// computed padding, a box's constraints. Such values never invalidate a
117    /// recompose scope when they change, so an equal key is the caller's
118    /// promise that the retained composition is not stale on that channel.
119    /// Values read from reactive state inside `content` need no key entry:
120    /// their writes invalidate the slot's scopes and block reuse. Content
121    /// must not read a non-reactive container (`Cell`, `RefCell`) for a value
122    /// that changes between measure passes unless that value is part of
123    /// `key`; debug builds recompose skipped slots and panic when the
124    /// retained topology diverges from a fresh composition.
125    fn subcompose<K, Content>(
126        &mut self,
127        slot_id: SlotId,
128        key: K,
129        content: Content,
130    ) -> Vec<SubcomposeChild>
131    where
132        K: PartialEq + 'static,
133        Content: FnMut() + 'static;
134
135    /// Measures a subcomposed child with the given constraints.
136    fn measure(&mut self, child: SubcomposeChild, constraints: Constraints) -> SubcomposePlaceable;
137
138    /// Checks if a node has no parent (is a root node).
139    /// Used to filter subcompose results to only include true root nodes.
140    fn node_has_no_parent(&self, node_id: NodeId) -> bool;
141}
142
143/// Concrete implementation of [`SubcomposeMeasureScope`].
144pub struct SubcomposeMeasureScopeImpl<'a> {
145    composer: Composer,
146    density_scope: crate::density::DensityMeasureScope,
147    state: &'a mut SubcomposeState,
148    constraints: Constraints,
149    measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
150    cached_measure_batch_registrar: CachedMeasureBatchRegistrar<'a>,
151    retained_measure_lookup: RetainedMeasureLookup<'a>,
152    retained_measure_registrar: RetainedMeasureRegistrar<'a>,
153    error: &'a RefCell<Option<NodeError>>,
154    parent_handle: SubcomposeLayoutNodeHandle,
155    root_id: NodeId,
156    placement_scratch: Vec<Placement>,
157    cached_measure_node_scratch: Vec<NodeId>,
158    cached_measure_size_scratch: Vec<Option<Size>>,
159    cached_measure_missing_scratch: Vec<NodeId>,
160    registered_measurement_node_ids: Vec<NodeId>,
161    pending_commands_applied: bool,
162    #[cfg(debug_assertions)]
163    shadow_stash: Option<(Vec<NodeId>, Vec<NodeId>)>,
164}
165
166thread_local! {
167    static CLEAN_SLOT_SKIPS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
168}
169
170fn record_clean_slot_skip() {
171    CLEAN_SLOT_SKIPS.with(|count| count.set(count.get() + 1));
172}
173
174/// Number of subcompose measure passes on this thread that reused a retained
175/// slot without a compose walk. Diagnostic surface for tests and telemetry;
176/// see [`SubcomposeMeasureScope::subcompose`] for when a slot may skip.
177pub fn clean_slot_skip_count() -> u64 {
178    CLEAN_SLOT_SKIPS.with(Cell::get)
179}
180
181struct SubcomposeMeasureScopeInit<'a> {
182    composer: Composer,
183    density: crate::density::Density,
184    state: &'a mut SubcomposeState,
185    constraints: Constraints,
186    measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
187    cached_measure_batch_registrar: CachedMeasureBatchRegistrar<'a>,
188    retained_measure_lookup: RetainedMeasureLookup<'a>,
189    retained_measure_registrar: RetainedMeasureRegistrar<'a>,
190    error: &'a RefCell<Option<NodeError>>,
191    parent_handle: SubcomposeLayoutNodeHandle,
192    root_id: NodeId,
193    placement_scratch: Vec<Placement>,
194}
195
196impl<'a> SubcomposeMeasureScopeImpl<'a> {
197    fn new(init: SubcomposeMeasureScopeInit<'a>) -> Self {
198        Self {
199            composer: init.composer,
200            density_scope: crate::density::DensityMeasureScope::new(init.density),
201            state: init.state,
202            constraints: init.constraints,
203            measurer: init.measurer,
204            cached_measure_batch_registrar: init.cached_measure_batch_registrar,
205            retained_measure_lookup: init.retained_measure_lookup,
206            retained_measure_registrar: init.retained_measure_registrar,
207            error: init.error,
208            parent_handle: init.parent_handle,
209            root_id: init.root_id,
210            placement_scratch: init.placement_scratch,
211            cached_measure_node_scratch: Vec::new(),
212            cached_measure_size_scratch: Vec::new(),
213            cached_measure_missing_scratch: Vec::new(),
214            registered_measurement_node_ids: Vec::new(),
215            pending_commands_applied: false,
216            #[cfg(debug_assertions)]
217            shadow_stash: None,
218        }
219    }
220
221    fn register_measurement_node_id(&mut self, node_id: NodeId) {
222        if !self.registered_measurement_node_ids.contains(&node_id) {
223            self.registered_measurement_node_ids.push(node_id);
224        }
225    }
226
227    fn into_placement_scratch(self) -> Vec<Placement> {
228        self.placement_scratch
229    }
230
231    pub(crate) fn layout_with_placement_builder(
232        &mut self,
233        width: f32,
234        height: f32,
235        build: impl FnOnce(&mut Vec<Placement>),
236    ) -> MeasureResult {
237        self.placement_scratch.clear();
238        build(&mut self.placement_scratch);
239        MeasureResult::new(
240            Size { width, height },
241            std::mem::take(&mut self.placement_scratch),
242        )
243    }
244
245    fn record_error(&self, err: NodeError) {
246        let mut slot = self.error.borrow_mut();
247        if slot.is_none() {
248            eprintln!("[SubcomposeLayout] Error suppressed: {err:?}");
249            *slot = Some(err);
250        }
251    }
252
253    fn owner_chain_deactivation_epoch(&self) -> u64 {
254        self.parent_handle
255            .inner
256            .borrow()
257            .captured_context
258            .as_ref()
259            .map_or(
260                0,
261                cranpose_core::CapturedCompositionContext::owner_chain_deactivation_epoch,
262            )
263    }
264
265    fn ensure_pending_commands_applied(&mut self) -> bool {
266        if self.pending_commands_applied {
267            return true;
268        }
269
270        let telemetry_start = subcompose_telemetry_enabled().then(Instant::now);
271        if let Err(err) = self.composer.apply_pending_commands() {
272            self.record_error(err);
273            return false;
274        }
275        if let Some(start) = telemetry_start {
276            log::warn!(
277                "[subcompose-telemetry] apply_pending_commands_ms={:.2}",
278                start.elapsed().as_secs_f64() * 1000.0
279            );
280        }
281
282        self.pending_commands_applied = true;
283        true
284    }
285
286    fn perform_subcompose<Content>(&mut self, slot_id: SlotId, content: Content) -> Vec<NodeId>
287    where
288        Content: FnMut() + 'static,
289    {
290        let telemetry_start = subcompose_telemetry_enabled().then(Instant::now);
291        let mut inner = self.parent_handle.inner.borrow_mut();
292
293        let (virtual_node_id, is_rebound) =
294            if let Some((node_id, rebound)) = self.state.take_node_from_reusables(slot_id) {
295                (node_id, rebound)
296            } else {
297                let id = allocate_virtual_node_id();
298                let node = LayoutNode::new_virtual();
299                if let Err(e) = self
300                    .composer
301                    .register_virtual_node(id, Box::new(node.clone()))
302                {
303                    eprintln!("[Subcompose] Failed to register virtual node {id}: {e:?}");
304                }
305                register_layout_node(id, &node);
306
307                inner.virtual_nodes.insert(id, Rc::new(node));
308                inner.children.push(id);
309                (id, false)
310            };
311
312        self.composer.record_subcompose_child(virtual_node_id);
313
314        if let Some(v_node) = inner.virtual_nodes.get(&virtual_node_id) {
315            v_node.set_parent(self.root_id);
316        }
317
318        drop(inner);
319
320        let children = self.compose_into_slot(slot_id, virtual_node_id, content);
321        if is_rebound {
322            self.composer.record_rebound_slot_children(&children);
323        }
324        if let Some(start) = telemetry_start {
325            log::warn!(
326                "[subcompose-telemetry] slot={} reused={} children={} subcompose_ms={:.2}",
327                slot_id.raw(),
328                is_rebound,
329                children.len(),
330                start.elapsed().as_secs_f64() * 1000.0
331            );
332        }
333        children
334    }
335
336    fn compose_into_slot<Content>(
337        &mut self,
338        slot_id: SlotId,
339        virtual_node_id: NodeId,
340        content: Content,
341    ) -> Vec<NodeId>
342    where
343        Content: FnMut() + 'static,
344    {
345        let content_holder = self.state.callback_holder(slot_id);
346        content_holder.update(content);
347
348        let _ = self
349            .composer
350            .with_node_mut::<LayoutNode, _>(virtual_node_id, |node| {
351                node.set_parent(self.root_id);
352            });
353
354        let slot_host = self.state.get_or_create_slots(slot_id);
355        self.parent_handle.note_slot_host(&slot_host);
356        let scopes = self
357            .composer
358            .subcompose_slot(&slot_host, Some(virtual_node_id), move |_| {
359                compose_subcompose_slot_content(content_holder);
360            })
361            .map(|((), scopes)| scopes)
362            .unwrap_or_default();
363        self.pending_commands_applied = false;
364
365        let owner_epoch = self.owner_chain_deactivation_epoch();
366        self.state
367            .register_active(slot_id, &[virtual_node_id], &scopes);
368        self.state.mark_slot_composed_current(slot_id, owner_epoch);
369
370        self.composer.get_node_children(virtual_node_id).to_vec()
371    }
372
373    fn activate_clean_retained_slot(&mut self, slot_id: SlotId) -> Option<Vec<NodeId>> {
374        if self.state.has_pending_precompositions(slot_id) {
375            return None;
376        }
377        if !self
378            .state
379            .slot_content_generation_current(slot_id, self.owner_chain_deactivation_epoch())
380        {
381            return None;
382        }
383        if !self.ensure_pending_commands_applied() {
384            return None;
385        }
386        let virtual_node_ids = self.state.activate_current_active_slot(slot_id)?;
387
388        {
389            let inner = self.parent_handle.inner.borrow();
390            for virtual_node_id in &virtual_node_ids {
391                self.composer.record_subcompose_child(*virtual_node_id);
392                if let Some(v_node) = inner.virtual_nodes.get(virtual_node_id) {
393                    v_node.set_parent(self.root_id);
394                }
395            }
396        }
397        for virtual_node_id in &virtual_node_ids {
398            let _ = self
399                .composer
400                .with_node_mut::<LayoutNode, _>(*virtual_node_id, |node| {
401                    node.set_parent(self.root_id);
402                });
403        }
404
405        let mut children = Vec::new();
406        for virtual_node_id in &virtual_node_ids {
407            children.extend(self.composer.get_node_children(*virtual_node_id));
408        }
409        record_clean_slot_skip();
410
411        #[cfg(debug_assertions)]
412        {
413            self.shadow_stash = Some((virtual_node_ids, children.clone()));
414        }
415
416        Some(children)
417    }
418
419    #[cfg(debug_assertions)]
420    fn shadow_verify_clean_slot<Content>(&mut self, slot_id: SlotId, content: Content)
421    where
422        Content: FnMut() + 'static,
423    {
424        let Some((virtual_node_ids, skipped_children)) = self.shadow_stash.take() else {
425            return;
426        };
427        if virtual_node_ids.len() != 1 {
428            return;
429        }
430        let composed = self.compose_into_slot(slot_id, virtual_node_ids[0], content);
431        assert!(
432            composed == skipped_children,
433            "clean-slot skip diverged for slot {slot_id:?}: recomposing produced root \
434             children {composed:?} but the retained slot held {skipped_children:?}. The slot content \
435             read a value that changed between measure passes without any \
436             invalidation path — make that value reactive state, or part of \
437             the subcompose capture key",
438        );
439    }
440
441    pub(crate) fn activate_exact_retained_slot_with_known_children(
442        &mut self,
443        slot_id: SlotId,
444        known_children: &[u64],
445    ) -> Option<(Vec<SubcomposeChild>, bool)> {
446        for &node_id in known_children {
447            NodeId::try_from(node_id).ok()?;
448        }
449
450        let virtual_node_ids = match self.activate_current_active_slot_roots(slot_id) {
451            Some(virtual_node_ids) => {
452                for virtual_node_id in &virtual_node_ids {
453                    self.composer.record_subcompose_child(*virtual_node_id);
454                }
455                virtual_node_ids
456            }
457            None => self.activate_recycled_exact_retained_slot_roots(slot_id)?,
458        };
459
460        if !self.ensure_pending_commands_applied() {
461            return None;
462        }
463
464        let mut activated_children = Vec::with_capacity(known_children.len());
465        for virtual_node_id in virtual_node_ids {
466            activated_children.extend(
467                self.composer
468                    .get_node_children(virtual_node_id)
469                    .iter()
470                    .copied()
471                    .map(SubcomposeChild::new),
472            );
473        }
474        let children_match = activated_children
475            .iter()
476            .map(|child| child.node_id() as u64)
477            .eq(known_children.iter().copied());
478        Some((activated_children, children_match))
479    }
480
481    fn activate_current_active_slot_roots(&mut self, slot_id: SlotId) -> Option<Vec<NodeId>> {
482        self.state.activate_current_active_slot(slot_id)
483    }
484
485    fn activate_recycled_exact_retained_slot_roots(
486        &mut self,
487        slot_id: SlotId,
488    ) -> Option<Vec<NodeId>> {
489        let activation = self.state.take_exact_slot_activation(slot_id)?;
490        let virtual_node_ids = activation.nodes;
491        let scopes = activation.scopes;
492        let reactivate_scopes = activation.reactivate_scopes;
493
494        if reactivate_scopes {
495            let inner = self.parent_handle.inner.borrow();
496            for virtual_node_id in &virtual_node_ids {
497                self.composer.record_subcompose_child(*virtual_node_id);
498                if let Some(v_node) = inner.virtual_nodes.get(virtual_node_id) {
499                    v_node.set_parent(self.root_id);
500                }
501            }
502            for virtual_node_id in &virtual_node_ids {
503                let _ = self
504                    .composer
505                    .with_node_mut::<LayoutNode, _>(*virtual_node_id, |node| {
506                        node.set_parent(self.root_id);
507                    });
508            }
509        } else {
510            for virtual_node_id in &virtual_node_ids {
511                self.composer.record_subcompose_child(*virtual_node_id);
512            }
513        }
514
515        self.state.register_active_with_scope_reactivation(
516            slot_id,
517            &virtual_node_ids,
518            &scopes,
519            reactivate_scopes,
520        );
521        Some(virtual_node_ids)
522    }
523}
524
525impl SubcomposeLayoutScope for SubcomposeMeasureScopeImpl<'_> {
526    fn constraints(&self) -> Constraints {
527        self.constraints
528    }
529
530    fn layout<I>(&mut self, width: f32, height: f32, placements: I) -> MeasureResult
531    where
532        I: IntoIterator<Item = Placement>,
533    {
534        self.layout_with_placement_builder(width, height, |scratch| {
535            scratch.extend(placements);
536        })
537    }
538}
539
540impl cranpose_ui_layout::MeasureScope for SubcomposeMeasureScopeImpl<'_> {
541    fn density(&self) -> f32 {
542        self.density_scope.density()
543    }
544
545    fn font_scale(&self) -> f32 {
546        self.density_scope.font_scale()
547    }
548}
549
550impl SubcomposeMeasureScope for SubcomposeMeasureScopeImpl<'_> {
551    fn subcompose<K, Content>(
552        &mut self,
553        slot_id: SlotId,
554        key: K,
555        content: Content,
556    ) -> Vec<SubcomposeChild>
557    where
558        K: PartialEq + 'static,
559        Content: FnMut() + 'static,
560    {
561        if self.state.retained_capture_key_matches(slot_id, &key)
562            && let Some(children) = self.activate_clean_retained_slot(slot_id)
563        {
564            #[cfg(debug_assertions)]
565            self.shadow_verify_clean_slot(slot_id, content);
566            return children.into_iter().map(SubcomposeChild::new).collect();
567        }
568        self.state.store_retained_capture_key(slot_id, key);
569        let nodes = self.perform_subcompose(slot_id, content);
570        nodes.into_iter().map(SubcomposeChild::new).collect()
571    }
572
573    fn measure(&mut self, child: SubcomposeChild, constraints: Constraints) -> SubcomposePlaceable {
574        if self.error.borrow().is_some() {
575            return SubcomposePlaceable::value(0.0, 0.0, child.node_id);
576        }
577
578        let telemetry_start = subcompose_telemetry_enabled().then(Instant::now);
579        if !self.ensure_pending_commands_applied() {
580            return SubcomposePlaceable::value(0.0, 0.0, child.node_id);
581        }
582
583        let size = (self.measurer)(child.node_id, constraints);
584        self.register_measurement_node_id(child.node_id);
585        if let Some(start) = telemetry_start {
586            log::warn!(
587                "[subcompose-telemetry] child={} measure_ms={:.2} size=({:.2},{:.2})",
588                child.node_id,
589                start.elapsed().as_secs_f64() * 1000.0,
590                size.width,
591                size.height
592            );
593        }
594        SubcomposePlaceable::value(size.width, size.height, child.node_id)
595    }
596
597    fn node_has_no_parent(&self, node_id: NodeId) -> bool {
598        self.composer.node_has_no_parent(node_id)
599    }
600}
601
602impl SubcomposeMeasureScopeImpl<'_> {
603    /// Returns the number of active slots in the subcompose state.
604    ///
605    /// Used by lazy layouts to report statistics about slot usage.
606    pub fn active_slots_count(&self) -> usize {
607        self.state.active_slots_count()
608    }
609
610    /// Returns the number of reusable slots in the pool.
611    ///
612    /// Used by lazy layouts to report statistics about cached slots.
613    pub fn reusable_slots_count(&self) -> usize {
614        self.state.reusable_slots_count()
615    }
616
617    /// Registers the content type for a slot.
618    ///
619    /// Call this before `subcompose()` to enable content-type-aware slot reuse.
620    /// If the policy supports content types (like `ContentTypeReusePolicy`),
621    /// slots with matching content types can reuse each other's nodes.
622    pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
623        self.state.register_content_type(slot_id, content_type);
624    }
625
626    /// Updates the content type for a slot, handling Some→None transitions.
627    ///
628    /// If `content_type` is `Some(type)`, registers the type for the slot.
629    /// If `content_type` is `None`, removes any previously registered type.
630    /// This ensures stale types don't drive incorrect reuse after transitions.
631    pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
632        self.state.update_content_type(slot_id, content_type);
633    }
634
635    pub(crate) fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
636        self.state.set_reusable_pool_limits(per_type, untyped);
637    }
638
639    pub(crate) fn focused_slot(&self) -> Option<SlotId> {
640        let mut node_id = crate::active_focus_target()?;
641        while node_id != self.root_id {
642            if let Some(slot) = self.state.active_slot_for_node(node_id) {
643                return Some(slot);
644            }
645            node_id = self.composer.node_parent(node_id).ok()??;
646        }
647        None
648    }
649
650    pub(crate) fn recycle_active_slots_where(&mut self, predicate: impl FnMut(SlotId) -> bool) {
651        let disposed = self.state.recycle_active_slots_where(predicate);
652        debug_assert!(
653            disposed.is_empty(),
654            "lazy subcompose reusable pool limits must retain recycled active slots"
655        );
656    }
657
658    /// Returns whether the last subcomposed slot was reused.
659    ///
660    /// Returns `Some(true)` if the slot already existed (was reused from pool or
661    /// was recomposed), `Some(false)` if it was newly created, or `None` if no
662    /// slot has been subcomposed yet this pass.
663    ///
664    /// This is useful for tracking composition statistics in lazy layouts.
665    pub fn was_last_slot_reused(&self) -> Option<bool> {
666        self.state.was_last_slot_reused()
667    }
668
669    pub(crate) fn measure_retained(
670        &mut self,
671        child: SubcomposeChild,
672        constraints: Constraints,
673    ) -> (SubcomposePlaceable, Option<Rc<MeasuredNode>>) {
674        let placeable = self.measure(child, constraints);
675        let retained = (self.retained_measure_lookup)(child.node_id);
676        (placeable, retained)
677    }
678
679    pub(crate) fn register_retained_measurements(&mut self, measurements: &[Rc<MeasuredNode>]) {
680        if measurements.is_empty() {
681            return;
682        }
683
684        for measured in measurements {
685            self.register_measurement_node_id(measured.node_id());
686        }
687        (self.retained_measure_registrar)(measurements);
688    }
689
690    pub(crate) fn children_need_relayout(&mut self, children: &[SubcomposeChild]) -> bool {
691        if !self.ensure_pending_commands_applied() {
692            return true;
693        }
694
695        let mut root_ids = smallvec::SmallVec::<[NodeId; 8]>::new();
696        root_ids.extend(children.iter().map(SubcomposeChild::node_id));
697        self.composer.nodes_need_measure(&root_ids) || self.composer.nodes_need_layout(&root_ids)
698    }
699
700    pub(crate) fn ensure_cached_measurement_node_ids<I>(
701        &mut self,
702        node_ids: I,
703        constraints: Constraints,
704    ) -> usize
705    where
706        I: IntoIterator<Item = NodeId>,
707    {
708        if self.error.borrow().is_some() || !self.ensure_pending_commands_applied() {
709            return 0;
710        }
711
712        self.cached_measure_node_scratch.clear();
713        self.cached_measure_node_scratch.extend(
714            node_ids
715                .into_iter()
716                .filter(|node_id| !self.registered_measurement_node_ids.contains(node_id)),
717        );
718        if self.cached_measure_node_scratch.is_empty() {
719            return 0;
720        }
721
722        self.cached_measure_size_scratch.clear();
723        (self.cached_measure_batch_registrar)(
724            &self.cached_measure_node_scratch,
725            constraints,
726            &mut self.cached_measure_size_scratch,
727        );
728        self.cached_measure_size_scratch
729            .resize(self.cached_measure_node_scratch.len(), None);
730
731        let mut cached_count = 0;
732        self.cached_measure_missing_scratch.clear();
733        for index in 0..self.cached_measure_node_scratch.len() {
734            let node_id = self.cached_measure_node_scratch[index];
735            if self.cached_measure_size_scratch[index].is_some() {
736                cached_count += 1;
737                self.register_measurement_node_id(node_id);
738            } else {
739                self.cached_measure_missing_scratch.push(node_id);
740            }
741        }
742
743        let mut missing = std::mem::take(&mut self.cached_measure_missing_scratch);
744        for node_id in missing.drain(..) {
745            let _ = self.measure(SubcomposeChild::new(node_id), constraints);
746        }
747        self.cached_measure_missing_scratch = missing;
748
749        cached_count
750    }
751}
752
753fn compose_subcompose_slot_content(holder: cranpose_core::CallbackHolder) {
754    cranpose_core::with_current_composer(|composer| {
755        let holder_for_recompose = holder.clone();
756        composer.set_recompose_callback(move |_composer| {
757            compose_subcompose_slot_content(holder_for_recompose.clone());
758        });
759    });
760
761    let invoke = holder.clone_rc();
762    invoke();
763}
764
765pub type MeasurePolicy =
766    dyn for<'scope> Fn(&mut SubcomposeMeasureScopeImpl<'scope>, Constraints) -> MeasureResult;
767
768/// Node responsible for orchestrating measure-time subcomposition.
769pub struct SubcomposeLayoutNode {
770    inner: Rc<RefCell<SubcomposeLayoutNodeInner>>,
771    parent: Cell<Option<NodeId>>,
772    id: Cell<Option<NodeId>>,
773    needs_measure: Cell<bool>,
774    needs_layout: Cell<bool>,
775    needs_semantics: Cell<bool>,
776    needs_redraw: Cell<bool>,
777    needs_pointer_pass: Cell<bool>,
778    needs_focus_sync: Cell<bool>,
779    virtual_children_count: Cell<usize>,
780    layout_state: RefCell<LayoutState>,
781    cache_handles: LayoutNodeCacheHandles,
782    modifier_slices_snapshot: RefCell<Rc<ModifierNodeSlices>>,
783    modifier_slices_dirty: Cell<bool>,
784}
785
786impl SubcomposeLayoutNode {
787    pub fn new(modifier: Modifier, measure_policy: Rc<MeasurePolicy>) -> Self {
788        let inner = Rc::new(RefCell::new(SubcomposeLayoutNodeInner::new(measure_policy)));
789        let node = Self {
790            inner,
791            parent: Cell::new(None),
792            id: Cell::new(None),
793            needs_measure: Cell::new(true),
794            needs_layout: Cell::new(true),
795            needs_semantics: Cell::new(true),
796            needs_redraw: Cell::new(true),
797            needs_pointer_pass: Cell::new(false),
798            needs_focus_sync: Cell::new(false),
799            virtual_children_count: Cell::new(0),
800            layout_state: RefCell::new(LayoutState::default()),
801            cache_handles: LayoutNodeCacheHandles::default(),
802            modifier_slices_snapshot: RefCell::new(Rc::default()),
803            modifier_slices_dirty: Cell::new(true),
804        };
805        let (invalidations, _) = node.inner.borrow_mut().set_modifier_collect(modifier);
806        node.dispatch_modifier_invalidations(&invalidations, NodeCapabilities::empty());
807        node.update_modifier_slices_cache();
808        node.note_host_to_the_composition_that_made_it();
809        node
810    }
811
812    fn note_host_to_the_composition_that_made_it(&self) {
813        let host = Rc::clone(&self.inner.borrow().slots);
814        cranpose_core::note_nested_slots_host(&host);
815    }
816
817    /// Creates a SubcomposeLayoutNode with ContentTypeReusePolicy.
818    ///
819    /// Use this for lazy lists to enable content-type-aware slot reuse.
820    /// Slots with matching content types can reuse each other's nodes,
821    /// improving efficiency when scrolling through items with different types.
822    pub fn with_content_type_policy(modifier: Modifier, measure_policy: Rc<MeasurePolicy>) -> Self {
823        let mut inner_data = SubcomposeLayoutNodeInner::new(measure_policy);
824        inner_data
825            .state
826            .set_policy(Box::new(cranpose_core::ContentTypeReusePolicy::new()));
827        let inner = Rc::new(RefCell::new(inner_data));
828        let node = Self {
829            inner,
830            parent: Cell::new(None),
831            id: Cell::new(None),
832            needs_measure: Cell::new(true),
833            needs_layout: Cell::new(true),
834            needs_semantics: Cell::new(true),
835            needs_redraw: Cell::new(true),
836            needs_pointer_pass: Cell::new(false),
837            needs_focus_sync: Cell::new(false),
838            virtual_children_count: Cell::new(0),
839            layout_state: RefCell::new(LayoutState::default()),
840            cache_handles: LayoutNodeCacheHandles::default(),
841            modifier_slices_snapshot: RefCell::new(Rc::default()),
842            modifier_slices_dirty: Cell::new(true),
843        };
844        let (invalidations, _) = node.inner.borrow_mut().set_modifier_collect(modifier);
845        node.dispatch_modifier_invalidations(&invalidations, NodeCapabilities::empty());
846        node.update_modifier_slices_cache();
847        node.note_host_to_the_composition_that_made_it();
848        node
849    }
850
851    pub fn handle(&self) -> SubcomposeLayoutNodeHandle {
852        SubcomposeLayoutNodeHandle {
853            inner: Rc::clone(&self.inner),
854        }
855    }
856
857    #[doc(hidden)]
858    pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
859        self.inner.borrow().state.debug_scope_ids_by_slot()
860    }
861
862    #[doc(hidden)]
863    pub fn debug_slot_table_for_slot(
864        &self,
865        slot_id: cranpose_core::SlotId,
866    ) -> Option<Vec<cranpose_core::SlotDebugEntry>> {
867        self.inner.borrow().state.debug_slot_table_for_slot(slot_id)
868    }
869
870    #[doc(hidden)]
871    pub fn debug_slot_table_groups_for_slot(
872        &self,
873        slot_id: cranpose_core::SlotId,
874    ) -> Option<Vec<cranpose_core::subcompose::DebugSlotGroup>> {
875        self.inner
876            .borrow()
877            .state
878            .debug_slot_table_groups_for_slot(slot_id)
879    }
880
881    pub fn set_measure_policy(&mut self, policy: Rc<MeasurePolicy>) {
882        let mut inner = self.inner.borrow_mut();
883        if Rc::ptr_eq(&inner.measure_policy, &policy) {
884            return;
885        }
886        inner.set_measure_policy(policy);
887        drop(inner);
888        self.invalidate_subcomposition();
889    }
890
891    /// Records the source composition context for measure-time subcomposition.
892    pub fn set_captured_context(&mut self, context: cranpose_core::CapturedCompositionContext) {
893        self.inner.borrow_mut().captured_context = Some(context);
894    }
895
896    /// Records the grid the composition provided, re-measuring if it moved.
897    ///
898    /// Mirrors [`LayoutNode::set_density`](crate::widgets::nodes::LayoutNode::set_density):
899    /// `SubcomposeLayout` captures this at the same composition site that
900    /// captures the subcomposition context, so the two cannot disagree.
901    pub fn set_density(&mut self, density: crate::density::Density) {
902        let mut inner = self.inner.borrow_mut();
903        if inner.density != density {
904            inner.density = density;
905            drop(inner);
906            self.mark_needs_measure();
907        }
908    }
909
910    pub fn set_modifier(&mut self, modifier: Modifier) {
911        let prev_caps = self.modifier_capabilities();
912        let (invalidations, modifier_changed) = {
913            let mut inner = self.inner.borrow_mut();
914            inner.set_modifier_collect(modifier)
915        };
916        self.dispatch_modifier_invalidations(&invalidations, prev_caps);
917        self.update_modifier_slices_cache();
918        if modifier_changed {
919            self.request_semantics_update();
920        }
921    }
922
923    fn update_modifier_slices_cache(&self) {
924        let inner = self.inner.borrow();
925        let mut snapshot = self.modifier_slices_snapshot.borrow_mut();
926        collect_modifier_slices_into(inner.modifier_chain.chain(), Rc::make_mut(&mut snapshot));
927        self.modifier_slices_dirty.set(false);
928    }
929
930    pub(crate) fn mark_modifier_slices_dirty(&self) {
931        self.modifier_slices_dirty.set(true);
932    }
933
934    pub fn set_debug_modifiers(&mut self, enabled: bool) {
935        self.inner.borrow_mut().set_debug_modifiers(enabled);
936    }
937
938    pub fn modifier(&self) -> Modifier {
939        self.handle().modifier()
940    }
941
942    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
943        self.inner.borrow().resolved_modifiers
944    }
945
946    /// Returns a clone of the current layout state.
947    pub fn layout_state(&self) -> LayoutState {
948        self.layout_state.borrow().clone()
949    }
950
951    pub(crate) fn cache_handles(&self) -> LayoutNodeCacheHandles {
952        self.cache_handles.clone()
953    }
954
955    /// Updates the position of this node. Called during placement.
956    /// [`LayoutState::place`] self-reports actual moves to the scene phase.
957    pub fn set_position(&self, position: Point) {
958        self.layout_state.borrow_mut().place(position);
959    }
960
961    /// Updates the measured size of this node. Called during measurement.
962    /// [`LayoutState::set_size`] self-reports actual changes to the scene
963    /// phase.
964    pub fn set_measured_size(&self, size: Size) {
965        self.layout_state.borrow_mut().set_size(size);
966    }
967
968    /// Clears the is_placed flag. Called at the start of a layout pass.
969    pub fn clear_placed(&self) {
970        self.layout_state.borrow_mut().clear_placed();
971    }
972
973    /// Returns the modifier slices snapshot for rendering.
974    pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
975        if self.modifier_slices_dirty.get() {
976            self.update_modifier_slices_cache();
977        }
978        self.modifier_slices_snapshot.borrow().clone()
979    }
980
981    pub fn state(&self) -> Ref<'_, SubcomposeState> {
982        Ref::map(self.inner.borrow(), |inner| &inner.state)
983    }
984
985    pub fn state_mut(&self) -> RefMut<'_, SubcomposeState> {
986        RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.state)
987    }
988
989    pub fn invalidate_subcomposition(&self) {
990        self.inner.borrow().state.invalidate_scopes();
991        self.mark_needs_measure();
992        if let Some(id) = self.id.get() {
993            cranpose_core::bubble_measure_dirty_in_composer(id);
994        }
995    }
996
997    pub fn request_measure_recompose(&self) {
998        self.mark_needs_measure();
999        if let Some(id) = self.id.get() {
1000            cranpose_core::bubble_measure_dirty_in_composer(id);
1001        }
1002    }
1003
1004    pub fn active_children(&self) -> Vec<NodeId> {
1005        current_subcompose_children(&self.inner.borrow())
1006    }
1007
1008    /// Mark this node as needing measure. Also marks it as needing layout.
1009    pub fn mark_needs_measure(&self) {
1010        self.needs_measure.set(true);
1011        self.needs_layout.set(true);
1012    }
1013
1014    /// Mark this node as needing layout (but not necessarily measure).
1015    pub fn mark_needs_layout_flag(&self) {
1016        self.needs_layout.set(true);
1017    }
1018
1019    /// Mark this node as needing redraw without forcing measure/layout.
1020    pub fn mark_needs_redraw(&self) {
1021        self.needs_redraw.set(true);
1022        if let Some(id) = self.id.get() {
1023            crate::schedule_draw_repass(id);
1024        }
1025        crate::request_render_invalidation();
1026    }
1027
1028    /// Check if this node needs measure.
1029    pub fn needs_measure(&self) -> bool {
1030        self.needs_measure.get()
1031    }
1032
1033    pub(crate) fn clear_needs_measure(&self) {
1034        self.needs_measure.set(false);
1035    }
1036
1037    pub(crate) fn clear_needs_layout(&self) {
1038        self.needs_layout.set(false);
1039    }
1040
1041    /// Mark this node as needing semantics recomputation.
1042    pub fn mark_needs_semantics(&self) {
1043        self.needs_semantics.set(true);
1044    }
1045
1046    pub(crate) fn clear_needs_semantics(&self) {
1047        self.needs_semantics.set(false);
1048    }
1049
1050    #[cfg(test)]
1051    pub(crate) fn clear_needs_semantics_for_tests(&self) {
1052        self.clear_needs_semantics();
1053    }
1054
1055    /// Returns true when this node requested a redraw since the last render pass.
1056    pub fn needs_redraw(&self) -> bool {
1057        self.needs_redraw.get()
1058    }
1059
1060    pub fn clear_needs_redraw(&self) {
1061        self.needs_redraw.set(false);
1062    }
1063
1064    /// Marks this node as needing a fresh pointer-input pass.
1065    pub fn mark_needs_pointer_pass(&self) {
1066        self.needs_pointer_pass.set(true);
1067    }
1068
1069    /// Returns true when pointer-input state needs to be recomputed.
1070    pub fn needs_pointer_pass(&self) -> bool {
1071        self.needs_pointer_pass.get()
1072    }
1073
1074    /// Clears the pointer-input dirty flag after hosts service it.
1075    pub fn clear_needs_pointer_pass(&self) {
1076        self.needs_pointer_pass.set(false);
1077    }
1078
1079    /// Marks this node as needing a focus synchronization.
1080    pub fn mark_needs_focus_sync(&self) {
1081        self.needs_focus_sync.set(true);
1082    }
1083
1084    /// Returns true when focus state needs to be synchronized.
1085    pub fn needs_focus_sync(&self) -> bool {
1086        self.needs_focus_sync.get()
1087    }
1088
1089    /// Clears the focus dirty flag after the focus manager processes it.
1090    pub fn clear_needs_focus_sync(&self) {
1091        self.needs_focus_sync.set(false);
1092    }
1093
1094    fn request_semantics_update(&self) {
1095        let already_dirty = self.needs_semantics.replace(true);
1096        if already_dirty {
1097            return;
1098        }
1099
1100        if let Some(id) = self.id.get() {
1101            cranpose_core::queue_semantics_invalidation(id);
1102        }
1103    }
1104
1105    /// Returns the modifier capabilities for this node.
1106    pub fn modifier_capabilities(&self) -> NodeCapabilities {
1107        self.inner.borrow().modifier_capabilities
1108    }
1109
1110    pub fn has_layout_modifier_nodes(&self) -> bool {
1111        self.modifier_capabilities()
1112            .contains(NodeCapabilities::LAYOUT)
1113    }
1114
1115    pub fn has_draw_modifier_nodes(&self) -> bool {
1116        self.modifier_capabilities()
1117            .contains(NodeCapabilities::DRAW)
1118    }
1119
1120    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1121        self.modifier_capabilities()
1122            .contains(NodeCapabilities::POINTER_INPUT)
1123    }
1124
1125    pub fn has_semantics_modifier_nodes(&self) -> bool {
1126        self.modifier_capabilities()
1127            .contains(NodeCapabilities::SEMANTICS)
1128    }
1129
1130    pub fn has_focus_modifier_nodes(&self) -> bool {
1131        self.modifier_capabilities()
1132            .contains(NodeCapabilities::FOCUS)
1133    }
1134
1135    fn dispatch_modifier_invalidations(
1136        &self,
1137        invalidations: &[ModifierInvalidation],
1138        prev_caps: NodeCapabilities,
1139    ) {
1140        let curr_caps = self.modifier_capabilities();
1141        for invalidation in invalidations {
1142            self.modifier_slices_dirty.set(true);
1143            let invalidation_caps = invalidation.capabilities();
1144            let has_capability = |capability| {
1145                curr_caps.contains(capability)
1146                    || prev_caps.contains(capability)
1147                    || invalidation_caps.contains(capability)
1148            };
1149            match invalidation.kind() {
1150                InvalidationKind::Layout => {
1151                    if has_capability(NodeCapabilities::LAYOUT) {
1152                        self.mark_needs_measure();
1153                    }
1154                }
1155                InvalidationKind::Draw => {
1156                    if has_capability(NodeCapabilities::DRAW) {
1157                        self.mark_needs_redraw();
1158                    }
1159                }
1160                InvalidationKind::PointerInput => {
1161                    if has_capability(NodeCapabilities::POINTER_INPUT) {
1162                        self.mark_needs_pointer_pass();
1163                        crate::request_pointer_invalidation();
1164                        if let Some(id) = self.id.get() {
1165                            crate::schedule_pointer_repass(id);
1166                        }
1167                    }
1168                }
1169                InvalidationKind::Semantics => {
1170                    self.request_semantics_update();
1171                }
1172                InvalidationKind::Focus => {
1173                    if has_capability(NodeCapabilities::FOCUS) {
1174                        self.mark_needs_focus_sync();
1175                        crate::request_focus_invalidation();
1176                        if let Some(id) = self.id.get() {
1177                            crate::schedule_focus_invalidation(id);
1178                        }
1179                    }
1180                }
1181            }
1182        }
1183    }
1184}
1185
1186impl cranpose_core::Node for SubcomposeLayoutNode {
1187    fn mount(&mut self) {
1188        let mut inner = self.inner.borrow_mut();
1189        let (chain, mut context) = inner.modifier_chain.chain_and_context_mut();
1190        chain.repair_chain();
1191        chain.attach_nodes(&mut *context);
1192    }
1193
1194    fn unmount(&mut self) {
1195        self.inner
1196            .borrow_mut()
1197            .modifier_chain
1198            .chain_mut()
1199            .detach_nodes();
1200    }
1201
1202    fn insert_child(&mut self, child: NodeId) -> bool {
1203        let mut inner = self.inner.borrow_mut();
1204        if inner.children.contains(&child) {
1205            return false;
1206        }
1207        if is_virtual_node(child) {
1208            let count = self.virtual_children_count.get();
1209            self.virtual_children_count.set(count + 1);
1210        }
1211        inner.children.push(child);
1212        true
1213    }
1214
1215    fn remove_child(&mut self, child: NodeId) -> bool {
1216        let mut inner = self.inner.borrow_mut();
1217        let before = inner.children.len();
1218        inner.children.retain(|&id| id != child);
1219        let removed = inner.children.len() < before;
1220        if removed && is_virtual_node(child) {
1221            let count = self.virtual_children_count.get();
1222            if count > 0 {
1223                self.virtual_children_count.set(count - 1);
1224            }
1225        }
1226        removed
1227    }
1228
1229    fn move_child(&mut self, from: usize, to: usize) {
1230        let mut inner = self.inner.borrow_mut();
1231        if from == to || from >= inner.children.len() {
1232            return;
1233        }
1234        let child = inner.children.remove(from);
1235        let target = to.min(inner.children.len());
1236        inner.children.insert(target, child);
1237    }
1238
1239    fn update_children(&mut self, children: &[NodeId]) {
1240        let mut inner = self.inner.borrow_mut();
1241        inner.children.clear();
1242        inner.children.extend_from_slice(children);
1243    }
1244
1245    fn children(&self) -> Vec<NodeId> {
1246        current_subcompose_children(&self.inner.borrow())
1247    }
1248
1249    fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1250        out.clear();
1251        out.extend(self.inner.borrow().last_placements.iter().copied());
1252    }
1253
1254    fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1255        out.clear();
1256        out.extend(self.inner.borrow().children.iter().copied());
1257    }
1258
1259    fn set_node_id(&mut self, id: NodeId) {
1260        self.id.set(Some(id));
1261        self.layout_state.borrow_mut().set_node_id(id);
1262        {
1263            let mut inner = self.inner.borrow_mut();
1264            inner.node_id = Some(id);
1265            inner.modifier_chain.set_node_id(Some(id));
1266        }
1267        self.update_modifier_slices_cache();
1268    }
1269
1270    fn on_attached_to_parent(&mut self, parent: NodeId) {
1271        self.parent.set(Some(parent));
1272    }
1273
1274    fn on_removed_from_parent(&mut self) {
1275        self.parent.set(None);
1276        self.inner.borrow().state.bump_content_generation();
1277    }
1278
1279    fn parent(&self) -> Option<NodeId> {
1280        self.parent.get()
1281    }
1282
1283    fn mark_needs_layout(&self) {
1284        self.needs_layout.set(true);
1285    }
1286
1287    fn needs_layout(&self) -> bool {
1288        self.needs_layout.get()
1289    }
1290
1291    fn mark_needs_measure(&self) {
1292        self.needs_measure.set(true);
1293        self.needs_layout.set(true);
1294    }
1295
1296    fn needs_measure(&self) -> bool {
1297        self.needs_measure.get()
1298    }
1299
1300    fn mark_needs_semantics(&self) {
1301        self.needs_semantics.set(true);
1302    }
1303
1304    fn needs_semantics(&self) -> bool {
1305        self.needs_semantics.get()
1306    }
1307
1308    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1309        if self.parent.get().is_none() {
1310            self.parent.set(Some(parent));
1311        }
1312    }
1313}
1314
1315#[derive(Clone)]
1316pub struct SubcomposeLayoutNodeHandle {
1317    inner: Rc<RefCell<SubcomposeLayoutNodeInner>>,
1318}
1319
1320impl SubcomposeLayoutNodeHandle {
1321    pub(crate) fn note_slot_host(&self, slot_host: &Rc<cranpose_core::SlotsHost>) {
1322        let Ok(inner) = self.inner.try_borrow() else {
1323            return;
1324        };
1325        if Rc::ptr_eq(&inner.slots, slot_host) {
1326            return;
1327        }
1328        inner.slots.note_nested_host(slot_host);
1329    }
1330
1331    pub(crate) fn measured_children_scratch(
1332        &self,
1333    ) -> Rc<RefCell<HashMap<NodeId, Rc<MeasuredNode>>>> {
1334        let scratch = {
1335            let inner = self.inner.borrow();
1336            Rc::clone(&inner.measured_children_scratch)
1337        };
1338        scratch.borrow_mut().clear();
1339        scratch
1340    }
1341
1342    pub fn modifier(&self) -> Modifier {
1343        self.inner.borrow().modifier.clone()
1344    }
1345
1346    pub fn layout_properties(&self) -> crate::modifier::LayoutProperties {
1347        self.resolved_modifiers().layout_properties()
1348    }
1349
1350    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1351        self.inner.borrow().resolved_modifiers
1352    }
1353
1354    pub fn total_offset(&self) -> Point {
1355        self.resolved_modifiers().offset()
1356    }
1357
1358    pub fn modifier_capabilities(&self) -> NodeCapabilities {
1359        self.inner.borrow().modifier_capabilities
1360    }
1361
1362    pub fn has_layout_modifier_nodes(&self) -> bool {
1363        self.modifier_capabilities()
1364            .contains(NodeCapabilities::LAYOUT)
1365    }
1366
1367    pub fn has_draw_modifier_nodes(&self) -> bool {
1368        self.modifier_capabilities()
1369            .contains(NodeCapabilities::DRAW)
1370    }
1371
1372    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1373        self.modifier_capabilities()
1374            .contains(NodeCapabilities::POINTER_INPUT)
1375    }
1376
1377    pub fn has_semantics_modifier_nodes(&self) -> bool {
1378        self.modifier_capabilities()
1379            .contains(NodeCapabilities::SEMANTICS)
1380    }
1381
1382    pub fn has_focus_modifier_nodes(&self) -> bool {
1383        self.modifier_capabilities()
1384            .contains(NodeCapabilities::FOCUS)
1385    }
1386
1387    pub fn set_debug_modifiers(&self, enabled: bool) {
1388        self.inner.borrow_mut().set_debug_modifiers(enabled);
1389    }
1390
1391    pub fn measure<'a>(
1392        &self,
1393        composer: &Composer,
1394        node_id: NodeId,
1395        constraints: Constraints,
1396        measurer: Box<dyn FnMut(NodeId, Constraints) -> Size + 'a>,
1397        mut cached_measure_registrar: Box<dyn FnMut(NodeId, Constraints) -> Option<Size> + 'a>,
1398        error: &'a RefCell<Option<NodeError>>,
1399    ) -> Result<MeasureResult, NodeError> {
1400        self.measure_with_cached_batch(
1401            composer,
1402            node_id,
1403            constraints,
1404            CachedBatchMeasureInputs {
1405                measurer,
1406                cached_measure_batch_registrar: Box::new(
1407                    move |node_ids, child_constraints, out| {
1408                        out.clear();
1409                        out.reserve(node_ids.len());
1410                        for &child_id in node_ids {
1411                            out.push(cached_measure_registrar(child_id, child_constraints));
1412                        }
1413                    },
1414                ),
1415                retained_measure_lookup: Box::new(|_| None),
1416                retained_measure_registrar: Box::new(|_| {}),
1417                error,
1418            },
1419        )
1420    }
1421
1422    pub(crate) fn measure_with_cached_batch(
1423        &self,
1424        composer: &Composer,
1425        node_id: NodeId,
1426        constraints: Constraints,
1427        callbacks: CachedBatchMeasureInputs<'_>,
1428    ) -> Result<MeasureResult, NodeError> {
1429        let CachedBatchMeasureInputs {
1430            measurer,
1431            cached_measure_batch_registrar,
1432            retained_measure_lookup,
1433            retained_measure_registrar,
1434            error,
1435        } = callbacks;
1436        let (policy, mut state, slots_host, placement_scratch, captured_context, density) = {
1437            let mut inner = self.inner.borrow_mut();
1438            let policy = Rc::clone(&inner.measure_policy);
1439            let state = std::mem::take(&mut inner.state);
1440            let slots_host = Rc::clone(&inner.slots);
1441            let placement_scratch = std::mem::take(&mut inner.placement_scratch);
1442            let captured_context = inner.captured_context.clone();
1443            let density = inner.density;
1444            (
1445                policy,
1446                state,
1447                slots_host,
1448                placement_scratch,
1449                captured_context,
1450                density,
1451            )
1452        };
1453        state.begin_pass();
1454
1455        let previous = composer.phase();
1456        if !matches!(previous, Phase::Measure | Phase::Layout) {
1457            composer.enter_phase(Phase::Measure);
1458        }
1459
1460        let constraints_copy = constraints;
1461        let fallback_context;
1462        let context = if let Some(context) = captured_context.as_ref() {
1463            context
1464        } else {
1465            fallback_context = composer.capture_composition_context();
1466            &fallback_context
1467        };
1468        let ((result, placement_scratch), _) = composer.subcompose_slot_with_context(
1469            &slots_host,
1470            Some(node_id),
1471            context,
1472            |inner_composer| {
1473                let mut scope = SubcomposeMeasureScopeImpl::new(SubcomposeMeasureScopeInit {
1474                    composer: inner_composer.clone(),
1475                    density,
1476                    state: &mut state,
1477                    constraints: constraints_copy,
1478                    measurer,
1479                    cached_measure_batch_registrar,
1480                    retained_measure_lookup,
1481                    retained_measure_registrar,
1482                    error,
1483                    parent_handle: self.clone(),
1484                    root_id: node_id,
1485                    placement_scratch,
1486                });
1487                let result = (policy)(&mut scope, constraints_copy);
1488                (result, scope.into_placement_scratch())
1489            },
1490        )?;
1491
1492        state.finish_pass();
1493
1494        if previous != composer.phase() {
1495            composer.enter_phase(previous);
1496        }
1497
1498        {
1499            let mut inner = self.inner.borrow_mut();
1500            inner.state = state;
1501            inner.placement_scratch = placement_scratch;
1502
1503            inner.replace_placed_children(
1504                result.placements.iter().map(|placement| placement.node_id),
1505            );
1506        }
1507
1508        Ok(result)
1509    }
1510
1511    pub(crate) fn recycle_placement_scratch(&self, mut placements: Vec<Placement>) {
1512        placements.clear();
1513        let mut inner = self.inner.borrow_mut();
1514        if placements.capacity() > inner.placement_scratch.capacity() {
1515            inner.placement_scratch = placements;
1516        }
1517    }
1518
1519    pub fn set_active_children<I>(&self, children: I)
1520    where
1521        I: IntoIterator<Item = NodeId>,
1522    {
1523        self.inner.borrow_mut().replace_placed_children(children);
1524    }
1525}
1526
1527fn current_subcompose_children(inner: &SubcomposeLayoutNodeInner) -> Vec<NodeId> {
1528    inner.last_placements.clone()
1529}
1530
1531struct SubcomposeLayoutNodeInner {
1532    modifier: Modifier,
1533    modifier_chain: ModifierChainHandle,
1534    resolved_modifiers: ResolvedModifiers,
1535    modifier_capabilities: NodeCapabilities,
1536    state: SubcomposeState,
1537    measure_policy: Rc<MeasurePolicy>,
1538    children: Vec<NodeId>,
1539    slots: Rc<SlotsHost>,
1540    debug_modifiers: bool,
1541    virtual_nodes: HashMap<NodeId, Rc<LayoutNode>>,
1542    node_id: Option<NodeId>,
1543    last_placements: Vec<NodeId>,
1544    placement_scratch: Vec<Placement>,
1545    measured_children_scratch: Rc<RefCell<HashMap<NodeId, Rc<MeasuredNode>>>>,
1546    captured_context: Option<cranpose_core::CapturedCompositionContext>,
1547    density: crate::density::Density,
1548}
1549
1550impl SubcomposeLayoutNodeInner {
1551    /// Writes the children this node placed, self-reporting an actual change
1552    /// to the scene phase the way [`LayoutState::place`] reports a move.
1553    ///
1554    /// These children *are* the node's render-graph children, and a
1555    /// subcomposition changes them without the applier seeing an insert or a
1556    /// remove: a lazy row that leaves the content keeps its nodes parked in
1557    /// the reusable pool, still attached and still carrying the position it
1558    /// was last placed at. Nothing else in the frame then says the child set
1559    /// shrank, so a scoped scene update would keep the departed row's layer
1560    /// and paint it under the row that took its place.
1561    fn replace_placed_children<I>(&mut self, children: I)
1562    where
1563        I: IntoIterator<Item = NodeId>,
1564    {
1565        let mut changed = false;
1566        let mut count = 0usize;
1567        for child in children {
1568            match self.last_placements.get(count) {
1569                Some(&placed) if placed == child => {}
1570                Some(_) => {
1571                    self.last_placements[count] = child;
1572                    changed = true;
1573                }
1574                None => {
1575                    self.last_placements.push(child);
1576                    changed = true;
1577                }
1578            }
1579            count += 1;
1580        }
1581        if self.last_placements.len() > count {
1582            self.last_placements.truncate(count);
1583            changed = true;
1584        }
1585        if changed && let Some(id) = self.node_id {
1586            crate::render_state::record_geometry_scene_node(id);
1587        }
1588    }
1589
1590    fn new(measure_policy: Rc<MeasurePolicy>) -> Self {
1591        Self {
1592            modifier: Modifier::empty(),
1593            modifier_chain: ModifierChainHandle::new(),
1594            resolved_modifiers: ResolvedModifiers::default(),
1595            modifier_capabilities: NodeCapabilities::default(),
1596            state: SubcomposeState::default(),
1597            measure_policy,
1598            children: Vec::new(),
1599            slots: Rc::new(SlotsHost::new(SlotTable::default())),
1600            debug_modifiers: false,
1601            virtual_nodes: HashMap::new(),
1602            node_id: None,
1603            last_placements: Vec::new(),
1604            placement_scratch: Vec::new(),
1605            measured_children_scratch: Rc::new(RefCell::new(HashMap::default())),
1606            captured_context: None,
1607            density: crate::density::Density::default(),
1608        }
1609    }
1610
1611    fn set_measure_policy(&mut self, policy: Rc<MeasurePolicy>) {
1612        self.measure_policy = policy;
1613        if let Err(err) = self.slots.reset() {
1614            log::error!(
1615                "failed to reset root measurement slots after measure policy update: {err}"
1616            );
1617        }
1618    }
1619
1620    fn set_modifier_collect(&mut self, modifier: Modifier) -> (Vec<ModifierInvalidation>, bool) {
1621        let modifier_changed = !self.modifier.structural_eq(&modifier);
1622        self.modifier = modifier;
1623        self.modifier_chain.set_debug_logging(self.debug_modifiers);
1624        let modifier_local_invalidations = self.modifier_chain.update(&self.modifier);
1625        self.resolved_modifiers = self.modifier_chain.resolved_modifiers();
1626        self.modifier_capabilities = self.modifier_chain.capabilities();
1627
1628        let mut invalidations = self.modifier_chain.take_invalidations();
1629        invalidations.extend(modifier_local_invalidations);
1630
1631        (invalidations, modifier_changed)
1632    }
1633
1634    fn set_debug_modifiers(&mut self, enabled: bool) {
1635        self.debug_modifiers = enabled;
1636        self.modifier_chain.set_debug_logging(enabled);
1637    }
1638}
1639
1640#[cfg(test)]
1641#[path = "tests/subcompose_layout_tests.rs"]
1642mod tests;