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