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