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