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