Skip to main content

cranpose_ui/
subcompose_layout.rs

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