Skip to main content

cranpose_ui/
subcompose_layout.rs

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