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