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