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