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            drop(inner);
908            self.modifier_slices_dirty.set(true);
909            self.mark_needs_measure();
910        }
911    }
912
913    /// The grid the composition provided, which the chain's lengths land on.
914    pub(crate) fn density(&self) -> crate::density::Density {
915        self.inner.borrow().density
916    }
917
918    /// Replaces the modifier chain and invalidates the layout, draw, pointer input and semantics
919    /// work that the changed modifier nodes affect.
920    pub fn set_modifier(&mut self, modifier: Modifier) {
921        let prev_caps = self.modifier_capabilities();
922        let (invalidations, modifier_changed) = {
923            let mut inner = self.inner.borrow_mut();
924            inner.set_modifier_collect(modifier)
925        };
926        self.dispatch_modifier_invalidations(&invalidations, prev_caps);
927        self.modifier_slices_dirty.set(true);
928        crate::modal_nodes::reach_changed(self.id.get());
929        if modifier_changed {
930            self.request_semantics_update();
931        }
932    }
933
934    fn update_modifier_slices_cache(&self) {
935        let inner = self.inner.borrow();
936        self.modifier_slices_snapshot.collect(
937            inner.modifier_chain.chain(),
938            &self.coordinator_geometry,
939            inner.density.density(),
940        );
941        self.modifier_slices_dirty.set(false);
942    }
943
944    pub(crate) fn mark_modifier_slices_dirty(&self) {
945        self.modifier_slices_dirty.set(true);
946    }
947
948    /// Turns debug logging of the modifier chain on or off.
949    pub fn set_debug_modifiers(&mut self, enabled: bool) {
950        self.inner.borrow_mut().set_debug_modifiers(enabled);
951    }
952
953    /// Returns the modifier that was last set on this node.
954    pub fn modifier(&self) -> Modifier {
955        self.handle().modifier()
956    }
957
958    /// Returns the padding, layout properties and offset that the modifier chain resolved to.
959    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
960        self.inner.borrow().modifier_chain.resolved_modifiers()
961    }
962
963    /// What the chain tells the parent's measure policy about this node.
964    pub(crate) fn parent_data(&self) -> cranpose_ui_layout::ParentData {
965        self.inner.borrow().modifier_chain.parent_data()
966    }
967
968    /// Returns a clone of the current layout state.
969    pub fn layout_state(&self) -> LayoutState {
970        self.layout_state.borrow().clone()
971    }
972
973    pub(crate) fn cache_handles(&self) -> &LayoutNodeCacheHandles {
974        &self.cache_handles
975    }
976
977    /// Updates the position of this node. Called during placement.
978    /// [`LayoutState::place`] self-reports actual moves to the scene phase.
979    pub fn set_position(&self, position: Point) {
980        self.layout_state.borrow_mut().place(position);
981    }
982
983    /// Updates the measured size of this node. Called during measurement.
984    /// [`LayoutState::set_size`] self-reports actual changes to the scene
985    /// phase.
986    pub fn set_measured_size(&self, size: Size) {
987        self.layout_state.borrow_mut().set_size(size);
988    }
989
990    /// Clears the is_placed flag. Called at the start of a layout pass.
991    pub fn clear_placed(&self) {
992        self.layout_state.borrow_mut().clear_placed();
993    }
994
995    /// Semantics reported by this node's live modifier chain.
996    pub fn semantics_configuration(&self) -> Option<SemanticsConfiguration> {
997        crate::modifier::collect_semantics_from_chain(self.inner.borrow().modifier_chain.chain())
998    }
999
1000    /// Whether this node's modifiers make it modal or hidden.
1001    pub fn semantics_reach(&self) -> cranpose_foundation::SemanticsReach {
1002        crate::modifier::semantics_reach_of_chain(self.inner.borrow().modifier_chain.chain())
1003    }
1004
1005    /// Returns the modifier slices snapshot for rendering.
1006    pub fn modifier_slices_snapshot(&self) -> Rc<ModifierNodeSlices> {
1007        if self.modifier_slices_dirty.get() {
1008            self.update_modifier_slices_cache();
1009        }
1010        self.modifier_slices_snapshot.get()
1011    }
1012
1013    /// Borrows the subcomposition state that tracks the slots composed for this node.
1014    pub fn state(&self) -> Ref<'_, SubcomposeState> {
1015        Ref::map(self.inner.borrow(), |inner| &inner.state)
1016    }
1017
1018    /// Mutably borrows the subcomposition state that tracks the slots composed for this node.
1019    pub fn state_mut(&self) -> RefMut<'_, SubcomposeState> {
1020        RefMut::map(self.inner.borrow_mut(), |inner| &mut inner.state)
1021    }
1022
1023    /// Invalidates the recompose scopes of all slots and schedules a measure, so the measure policy
1024    /// subcomposes again.
1025    pub fn invalidate_subcomposition(&self) {
1026        self.inner.borrow().state.invalidate_scopes();
1027        self.mark_needs_measure();
1028        if let Some(id) = self.id.get() {
1029            cranpose_core::bubble_measure_dirty_in_composer(id);
1030        }
1031    }
1032
1033    /// Schedules a measure for this node and its ancestors without invalidating the recompose
1034    /// scopes of its slots.
1035    pub fn request_measure_recompose(&self) {
1036        self.mark_needs_measure();
1037        if let Some(id) = self.id.get() {
1038            cranpose_core::bubble_measure_dirty_in_composer(id);
1039        }
1040    }
1041
1042    /// Calls `f` with the children this node placed in its last layout, in
1043    /// placement order, without copying them.
1044    /// Changes through a node handle during `f` become visible to the next
1045    /// read; this read keeps its original children.
1046    pub fn with_active_children<R>(&self, f: impl FnOnce(&[NodeId]) -> R) -> R {
1047        let children = Rc::clone(&self.inner.borrow().last_placements);
1048        f(&children)
1049    }
1050
1051    /// Mark this node as needing measure. Also marks it as needing layout.
1052    pub fn mark_needs_measure(&self) {
1053        self.needs_measure.set(true);
1054        self.needs_layout.set(true);
1055    }
1056
1057    /// Mark this node as needing layout (but not necessarily measure).
1058    pub fn mark_needs_layout_flag(&self) {
1059        self.needs_layout.set(true);
1060    }
1061
1062    /// Mark this node as needing redraw without forcing measure/layout.
1063    pub fn mark_needs_redraw(&self) {
1064        self.needs_redraw.set(true);
1065        if let Some(id) = self.id.get() {
1066            crate::schedule_draw_repass(id);
1067        }
1068        crate::request_render_invalidation();
1069    }
1070
1071    /// Check if this node needs measure.
1072    pub fn needs_measure(&self) -> bool {
1073        self.needs_measure.get()
1074    }
1075
1076    pub(crate) fn clear_needs_measure(&self) {
1077        self.needs_measure.set(false);
1078    }
1079
1080    /// Clears the node's layout flag and its descendants': a layout pass
1081    /// visited them.
1082    pub(crate) fn clear_needs_layout(&self) {
1083        self.needs_layout.set(false);
1084        self.descendant_dirt.set(DescendantDirt::default());
1085    }
1086
1087    /// Mark this node as needing semantics recomputation.
1088    pub fn mark_needs_semantics(&self) {
1089        self.needs_semantics.set(true);
1090        crate::modal_nodes::reach_changed(self.id.get());
1091    }
1092
1093    /// The id this node was inserted under, once it is.
1094    pub(crate) fn id(&self) -> Option<NodeId> {
1095        self.id.get()
1096    }
1097
1098    pub(crate) fn clear_needs_semantics(&self) {
1099        self.needs_semantics.set(false);
1100    }
1101
1102    #[cfg(test)]
1103    pub(crate) fn clear_needs_semantics_for_tests(&self) {
1104        self.clear_needs_semantics();
1105    }
1106
1107    /// Returns true when this node requested a redraw since the last render pass.
1108    pub fn needs_redraw(&self) -> bool {
1109        self.needs_redraw.get()
1110    }
1111
1112    /// Clears the pending redraw flag once the node has been drawn.
1113    pub fn clear_needs_redraw(&self) {
1114        self.needs_redraw.set(false);
1115    }
1116
1117    /// Marks this node as needing a fresh pointer-input pass.
1118    pub fn mark_needs_pointer_pass(&self) {
1119        self.needs_pointer_pass.set(true);
1120    }
1121
1122    /// Returns true when pointer-input state needs to be recomputed.
1123    pub fn needs_pointer_pass(&self) -> bool {
1124        self.needs_pointer_pass.get()
1125    }
1126
1127    /// Clears the pointer-input dirty flag after hosts service it.
1128    pub fn clear_needs_pointer_pass(&self) {
1129        self.needs_pointer_pass.set(false);
1130    }
1131
1132    /// Marks this node as needing a focus synchronization.
1133    pub fn mark_needs_focus_sync(&self) {
1134        self.needs_focus_sync.set(true);
1135    }
1136
1137    /// Returns true when focus state needs to be synchronized.
1138    pub fn needs_focus_sync(&self) -> bool {
1139        self.needs_focus_sync.get()
1140    }
1141
1142    /// Clears the focus dirty flag after the focus manager processes it.
1143    pub fn clear_needs_focus_sync(&self) {
1144        self.needs_focus_sync.set(false);
1145    }
1146
1147    fn request_semantics_update(&self) {
1148        let already_dirty = self.needs_semantics.replace(true);
1149        if already_dirty {
1150            return;
1151        }
1152
1153        if let Some(id) = self.id.get() {
1154            cranpose_core::queue_semantics_invalidation(id);
1155        }
1156    }
1157
1158    /// Returns the modifier capabilities for this node.
1159    pub fn modifier_capabilities(&self) -> NodeCapabilities {
1160        self.inner.borrow().modifier_capabilities
1161    }
1162
1163    /// Returns whether the modifier chain contains layout modifier nodes.
1164    pub fn has_layout_modifier_nodes(&self) -> bool {
1165        self.modifier_capabilities()
1166            .contains(NodeCapabilities::LAYOUT)
1167    }
1168
1169    /// Returns whether the modifier chain contains draw modifier nodes.
1170    pub fn has_draw_modifier_nodes(&self) -> bool {
1171        self.modifier_capabilities()
1172            .contains(NodeCapabilities::DRAW)
1173    }
1174
1175    /// Returns whether the modifier chain contains pointer input modifier nodes.
1176    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1177        self.modifier_capabilities()
1178            .contains(NodeCapabilities::POINTER_INPUT)
1179    }
1180
1181    /// Returns whether the modifier chain contains semantics modifier nodes.
1182    pub fn has_semantics_modifier_nodes(&self) -> bool {
1183        self.modifier_capabilities()
1184            .contains(NodeCapabilities::SEMANTICS)
1185    }
1186
1187    /// Returns whether the modifier chain contains focus modifier nodes.
1188    pub fn has_focus_modifier_nodes(&self) -> bool {
1189        self.modifier_capabilities()
1190            .contains(NodeCapabilities::FOCUS)
1191    }
1192
1193    fn dispatch_modifier_invalidations(
1194        &self,
1195        invalidations: &[ModifierInvalidation],
1196        prev_caps: NodeCapabilities,
1197    ) {
1198        let curr_caps = self.modifier_capabilities();
1199        for invalidation in invalidations {
1200            self.modifier_slices_dirty.set(true);
1201            let invalidation_caps = invalidation.capabilities();
1202            let has_capability = |capability| {
1203                curr_caps.contains(capability)
1204                    || prev_caps.contains(capability)
1205                    || invalidation_caps.contains(capability)
1206            };
1207            match invalidation.kind() {
1208                InvalidationKind::Layout => {
1209                    if has_capability(NodeCapabilities::LAYOUT) {
1210                        self.mark_needs_measure();
1211                    }
1212                }
1213                InvalidationKind::Draw => {
1214                    if has_capability(NodeCapabilities::DRAW) {
1215                        self.mark_needs_redraw();
1216                    }
1217                }
1218                InvalidationKind::PointerInput => {
1219                    if has_capability(NodeCapabilities::POINTER_INPUT) {
1220                        self.mark_needs_pointer_pass();
1221                        crate::request_pointer_invalidation();
1222                        if let Some(id) = self.id.get() {
1223                            crate::schedule_pointer_repass(id);
1224                        }
1225                    }
1226                }
1227                InvalidationKind::Semantics => {
1228                    self.request_semantics_update();
1229                }
1230                InvalidationKind::Focus => {
1231                    if has_capability(NodeCapabilities::FOCUS) {
1232                        self.mark_needs_focus_sync();
1233                        crate::request_focus_invalidation();
1234                        if let Some(id) = self.id.get() {
1235                            crate::schedule_focus_invalidation(id);
1236                        }
1237                    }
1238                }
1239            }
1240        }
1241    }
1242}
1243
1244impl cranpose_core::Node for SubcomposeLayoutNode {
1245    fn mount(&mut self) {
1246        let mut inner = self.inner.borrow_mut();
1247        let node_id = inner.node_id;
1248        inner.modifier_chain.remount(node_id);
1249        crate::modal_nodes::reach_changed(self.id.get());
1250    }
1251
1252    fn unmount(&mut self) {
1253        self.inner
1254            .borrow_mut()
1255            .modifier_chain
1256            .chain_mut()
1257            .detach_nodes();
1258    }
1259
1260    fn insert_child(&mut self, child: NodeId) -> bool {
1261        let mut inner = self.inner.borrow_mut();
1262        if inner.children.contains(&child) {
1263            return false;
1264        }
1265        inner.children.push(child);
1266        true
1267    }
1268
1269    fn remove_child(&mut self, child: NodeId) -> bool {
1270        let mut inner = self.inner.borrow_mut();
1271        let before = inner.children.len();
1272        inner.children.retain(|&id| id != child);
1273        inner.children.len() < before
1274    }
1275
1276    fn move_child(&mut self, from: usize, to: usize) {
1277        let mut inner = self.inner.borrow_mut();
1278        if from == to || from >= inner.children.len() {
1279            return;
1280        }
1281        let child = inner.children.remove(from);
1282        let target = to.min(inner.children.len());
1283        inner.children.insert(target, child);
1284    }
1285
1286    fn update_children(&mut self, children: &[NodeId]) {
1287        let mut inner = self.inner.borrow_mut();
1288        inner.children.clear();
1289        inner.children.extend_from_slice(children);
1290    }
1291
1292    fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1293        out.clear();
1294        self.with_active_children(|children| out.extend_from_slice(children));
1295    }
1296
1297    fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
1298        out.clear();
1299        out.extend_from_slice(&self.inner.borrow().children);
1300    }
1301
1302    fn owned_child_index(&self, child: NodeId) -> Option<usize> {
1303        self.inner
1304            .borrow()
1305            .children
1306            .iter()
1307            .position(|&id| id == child)
1308    }
1309
1310    fn set_node_id(&mut self, id: NodeId) {
1311        self.id.set(Some(id));
1312        self.layout_state.borrow_mut().set_node_id(id);
1313        {
1314            let mut inner = self.inner.borrow_mut();
1315            let previous = inner.node_id.replace(id);
1316            inner.modifier_chain.move_to_node_later(previous, Some(id));
1317        }
1318        // The slices carry the node's id; they are collected again when next read.
1319        self.modifier_slices_dirty.set(true);
1320    }
1321
1322    fn on_attached_to_parent(&mut self, parent: NodeId) {
1323        self.parent.set(Some(parent));
1324    }
1325
1326    fn on_removed_from_parent(&mut self) {
1327        self.parent.set(None);
1328        self.inner.borrow().state.bump_content_generation();
1329    }
1330
1331    fn parent(&self) -> Option<NodeId> {
1332        self.parent.get()
1333    }
1334
1335    fn mark_needs_layout(&self) {
1336        self.needs_layout.set(true);
1337    }
1338
1339    fn needs_layout(&self) -> bool {
1340        self.needs_layout.get()
1341    }
1342
1343    fn mark_needs_measure(&self) {
1344        self.needs_measure.set(true);
1345        self.needs_layout.set(true);
1346    }
1347
1348    fn needs_measure(&self) -> bool {
1349        self.needs_measure.get()
1350    }
1351
1352    fn mark_descendant_needs_layout(&self, measure: bool) {
1353        self.descendant_dirt
1354            .set(self.descendant_dirt.get().marked(measure));
1355        if measure {
1356            self.cache_handles.forget_intrinsics();
1357        }
1358    }
1359
1360    fn descendant_needs_layout(&self) -> bool {
1361        self.descendant_dirt.get().layout
1362    }
1363
1364    fn descendant_needs_measure(&self) -> bool {
1365        self.descendant_dirt.get().measure
1366    }
1367
1368    fn mark_needs_semantics(&self) {
1369        self.needs_semantics.set(true);
1370        crate::modal_nodes::reach_changed(self.id.get());
1371    }
1372
1373    /// One flag serves both: the tree merges a subcompose node on every
1374    /// update, and a descendant's change leaves its reach as it was.
1375    fn mark_descendant_needs_semantics(&self) {
1376        self.needs_semantics.set(true);
1377    }
1378
1379    fn needs_semantics(&self) -> bool {
1380        self.needs_semantics.get()
1381    }
1382
1383    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
1384        if self.parent.get().is_none() {
1385            self.parent.set(Some(parent));
1386        }
1387    }
1388}
1389
1390#[derive(Clone)]
1391pub struct SubcomposeLayoutNodeHandle {
1392    inner: Rc<RefCell<SubcomposeLayoutNodeInner>>,
1393}
1394
1395impl SubcomposeLayoutNodeHandle {
1396    pub(crate) fn note_slot_host(&self, slot_host: &Rc<cranpose_core::SlotsHost>) {
1397        let Ok(inner) = self.inner.try_borrow() else {
1398            return;
1399        };
1400        if Rc::ptr_eq(&inner.slots, slot_host) {
1401            return;
1402        }
1403        inner.slots.note_nested_host(slot_host);
1404    }
1405
1406    pub(crate) fn measured_children_scratch(
1407        &self,
1408    ) -> Rc<RefCell<HashMap<NodeId, Rc<MeasuredNode>>>> {
1409        let scratch = {
1410            let inner = self.inner.borrow();
1411            Rc::clone(&inner.measured_children_scratch)
1412        };
1413        scratch.borrow_mut().clear();
1414        scratch
1415    }
1416
1417    pub fn modifier(&self) -> Modifier {
1418        self.inner.borrow().modifier.clone()
1419    }
1420
1421    pub fn layout_properties(&self) -> crate::modifier::LayoutProperties {
1422        self.resolved_modifiers().layout_properties()
1423    }
1424
1425    pub fn resolved_modifiers(&self) -> ResolvedModifiers {
1426        self.inner.borrow().modifier_chain.resolved_modifiers()
1427    }
1428
1429    pub fn total_offset(&self) -> Point {
1430        self.resolved_modifiers().offset()
1431    }
1432
1433    pub fn modifier_capabilities(&self) -> NodeCapabilities {
1434        self.inner.borrow().modifier_capabilities
1435    }
1436
1437    pub fn has_layout_modifier_nodes(&self) -> bool {
1438        self.modifier_capabilities()
1439            .contains(NodeCapabilities::LAYOUT)
1440    }
1441
1442    pub fn has_draw_modifier_nodes(&self) -> bool {
1443        self.modifier_capabilities()
1444            .contains(NodeCapabilities::DRAW)
1445    }
1446
1447    pub fn has_pointer_input_modifier_nodes(&self) -> bool {
1448        self.modifier_capabilities()
1449            .contains(NodeCapabilities::POINTER_INPUT)
1450    }
1451
1452    pub fn has_semantics_modifier_nodes(&self) -> bool {
1453        self.modifier_capabilities()
1454            .contains(NodeCapabilities::SEMANTICS)
1455    }
1456
1457    pub fn has_focus_modifier_nodes(&self) -> bool {
1458        self.modifier_capabilities()
1459            .contains(NodeCapabilities::FOCUS)
1460    }
1461
1462    pub fn set_debug_modifiers(&self, enabled: bool) {
1463        self.inner.borrow_mut().set_debug_modifiers(enabled);
1464    }
1465
1466    pub fn measure<'a>(
1467        &self,
1468        composer: &Composer,
1469        node_id: NodeId,
1470        constraints: Constraints,
1471        measurer: Box<dyn FnMut(NodeId, Constraints) -> SubcomposePlaceable + 'a>,
1472        mut cached_measure_registrar: Box<dyn FnMut(NodeId, Constraints) -> Option<Size> + 'a>,
1473        error: &'a RefCell<Option<NodeError>>,
1474    ) -> Result<MeasureResult, NodeError> {
1475        self.measure_with_cached_batch(
1476            composer,
1477            node_id,
1478            constraints,
1479            CachedBatchMeasureInputs {
1480                measurer,
1481                cached_measure_batch_registrar: Box::new(
1482                    move |node_ids, child_constraints, out| {
1483                        out.clear();
1484                        out.reserve(node_ids.len());
1485                        for &child_id in node_ids {
1486                            out.push(cached_measure_registrar(child_id, child_constraints));
1487                        }
1488                    },
1489                ),
1490                retained_measure_lookup: Box::new(|_| None),
1491                retained_measure_registrar: Box::new(|_| {}),
1492                error,
1493            },
1494        )
1495    }
1496
1497    pub(crate) fn measure_with_cached_batch(
1498        &self,
1499        composer: &Composer,
1500        node_id: NodeId,
1501        constraints: Constraints,
1502        callbacks: CachedBatchMeasureInputs<'_>,
1503    ) -> Result<MeasureResult, NodeError> {
1504        let CachedBatchMeasureInputs {
1505            measurer,
1506            cached_measure_batch_registrar,
1507            retained_measure_lookup,
1508            retained_measure_registrar,
1509            error,
1510        } = callbacks;
1511        let (policy, mut state, slots_host, placement_scratch, captured_context, density) = {
1512            let mut inner = self.inner.borrow_mut();
1513            let policy = Rc::clone(&inner.measure_policy);
1514            let state = std::mem::take(&mut inner.state);
1515            let slots_host = Rc::clone(&inner.slots);
1516            let placement_scratch = std::mem::take(&mut inner.placement_scratch);
1517            let captured_context = inner.captured_context.clone();
1518            let density = inner.density;
1519            (
1520                policy,
1521                state,
1522                slots_host,
1523                placement_scratch,
1524                captured_context,
1525                density,
1526            )
1527        };
1528        state.begin_pass();
1529
1530        let previous = composer.phase();
1531        if !matches!(previous, Phase::Measure | Phase::Layout) {
1532            composer.enter_phase(Phase::Measure);
1533        }
1534
1535        let constraints_copy = constraints;
1536        let fallback_context;
1537        let context = if let Some(context) = captured_context.as_ref() {
1538            context
1539        } else {
1540            fallback_context = composer.capture_composition_context();
1541            &fallback_context
1542        };
1543        let ((result, placement_scratch), _) = composer.subcompose_slot_with_context(
1544            &slots_host,
1545            Some(node_id),
1546            context,
1547            |inner_composer| {
1548                let mut scope = SubcomposeMeasureScopeImpl::new(SubcomposeMeasureScopeInit {
1549                    composer: inner_composer.clone(),
1550                    density,
1551                    state: &mut state,
1552                    constraints: constraints_copy,
1553                    measurer,
1554                    cached_measure_batch_registrar,
1555                    retained_measure_lookup,
1556                    retained_measure_registrar,
1557                    error,
1558                    parent_handle: self.clone(),
1559                    root_id: node_id,
1560                    placement_scratch,
1561                });
1562                let result = (policy)(&mut scope, constraints_copy);
1563                (result, scope.into_placement_scratch())
1564            },
1565        )?;
1566
1567        let disposed = state.finish_pass();
1568        let disposal = composer.dispose_subcomposed_nodes(disposed);
1569
1570        if previous != composer.phase() {
1571            composer.enter_phase(previous);
1572        }
1573
1574        {
1575            let mut inner = self.inner.borrow_mut();
1576            inner.state = state;
1577            inner.placement_scratch = placement_scratch;
1578
1579            inner.replace_placed_children(
1580                result.placements.iter().map(|placement| placement.node_id),
1581            );
1582        }
1583
1584        disposal?;
1585        Ok(result)
1586    }
1587
1588    pub(crate) fn recycle_placement_scratch(&self, mut placements: Vec<Placement>) {
1589        placements.clear();
1590        let mut inner = self.inner.borrow_mut();
1591        if placements.capacity() > inner.placement_scratch.capacity() {
1592            inner.placement_scratch = placements;
1593        }
1594    }
1595
1596    pub fn set_active_children<I>(&self, children: I)
1597    where
1598        I: IntoIterator<Item = NodeId>,
1599    {
1600        self.inner.borrow_mut().replace_placed_children(children);
1601    }
1602}
1603
1604struct SubcomposeLayoutNodeInner {
1605    modifier: Modifier,
1606    modifier_chain: ModifierChainHandle,
1607    modifier_capabilities: NodeCapabilities,
1608    state: SubcomposeState,
1609    measure_policy: Rc<MeasurePolicy>,
1610    children: Vec<NodeId>,
1611    slots: Rc<SlotsHost>,
1612    debug_modifiers: bool,
1613    node_id: Option<NodeId>,
1614    last_placements: Rc<Vec<NodeId>>,
1615    placement_scratch: Vec<Placement>,
1616    measured_children_scratch: Rc<RefCell<HashMap<NodeId, Rc<MeasuredNode>>>>,
1617    captured_context: Option<cranpose_core::CapturedCompositionContext>,
1618    density: crate::density::Density,
1619}
1620
1621impl SubcomposeLayoutNodeInner {
1622    /// Writes the children this node placed, self-reporting an actual change
1623    /// to the scene phase the way [`LayoutState::place`] reports a move.
1624    ///
1625    /// These children *are* the node's render-graph children, and a
1626    /// subcomposition changes them without the applier seeing an insert or a
1627    /// remove: a lazy row that leaves the content keeps its nodes parked in
1628    /// the reusable pool, still attached and still carrying the position it
1629    /// was last placed at. Nothing else in the frame then says the child set
1630    /// shrank, so a scoped scene update would keep the departed row's layer
1631    /// and paint it under the row that took its place.
1632    fn replace_placed_children<I>(&mut self, children: I)
1633    where
1634        I: IntoIterator<Item = NodeId>,
1635    {
1636        let mut changed = false;
1637        let mut count = 0usize;
1638        for child in children {
1639            match self.last_placements.get(count) {
1640                Some(&placed) if placed == child => {}
1641                Some(_) => {
1642                    Rc::make_mut(&mut self.last_placements)[count] = child;
1643                    changed = true;
1644                }
1645                None => {
1646                    Rc::make_mut(&mut self.last_placements).push(child);
1647                    changed = true;
1648                }
1649            }
1650            count += 1;
1651        }
1652        if self.last_placements.len() > count {
1653            Rc::make_mut(&mut self.last_placements).truncate(count);
1654            changed = true;
1655        }
1656        if changed && let Some(id) = self.node_id {
1657            crate::render_state::record_geometry_scene_node(id);
1658        }
1659    }
1660
1661    fn new(measure_policy: Rc<MeasurePolicy>) -> Self {
1662        Self {
1663            modifier: Modifier::empty(),
1664            modifier_chain: ModifierChainHandle::detached(),
1665            modifier_capabilities: NodeCapabilities::default(),
1666            state: SubcomposeState::default(),
1667            measure_policy,
1668            children: Vec::new(),
1669            slots: Rc::new(SlotsHost::new(SlotTable::default())),
1670            debug_modifiers: false,
1671            node_id: None,
1672            last_placements: Rc::new(Vec::new()),
1673            placement_scratch: Vec::new(),
1674            measured_children_scratch: Rc::new(RefCell::new(HashMap::default())),
1675            captured_context: None,
1676            density: crate::density::Density::default(),
1677        }
1678    }
1679
1680    fn set_measure_policy(&mut self, policy: Rc<MeasurePolicy>) {
1681        self.measure_policy = policy;
1682        if let Err(err) = self.slots.reset() {
1683            log::error!(
1684                "failed to reset root measurement slots after measure policy update: {err}"
1685            );
1686        }
1687    }
1688
1689    fn set_modifier_collect(&mut self, modifier: Modifier) -> (ModifierInvalidations, bool) {
1690        let modifier_changed = !self.modifier.structural_eq(&modifier);
1691        self.modifier = modifier;
1692        self.modifier_chain.set_debug_logging(self.debug_modifiers);
1693        let invalidations = self
1694            .modifier_chain
1695            .update_on_node(self.node_id, &self.modifier);
1696        self.modifier_capabilities = self.modifier_chain.capabilities();
1697
1698        (invalidations, modifier_changed)
1699    }
1700
1701    fn set_debug_modifiers(&mut self, enabled: bool) {
1702        self.debug_modifiers = enabled;
1703        self.modifier_chain.set_debug_logging(enabled);
1704    }
1705}
1706
1707#[cfg(test)]
1708#[path = "tests/subcompose_layout_tests.rs"]
1709mod tests;