Skip to main content

cranpose_ui/
subcompose_layout.rs

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