Skip to main content

tessera_ui/
layout.rs

1//! Layout policies and measurement.
2//!
3//! ## Usage
4//!
5//! Measure and align content while reserving independent interaction bounds.
6
7use std::{
8    any::Any,
9    collections::HashMap,
10    hash::{Hash, Hasher},
11    sync::Arc,
12};
13
14use crate::{
15    ComputedData, Constraint, MeasurementError, ParentConstraint, Px, RenderSlot,
16    component_tree::{
17        ComponentNodeMetaData, ComponentNodeMetaDatas, ComponentNodeTree, DrawLayoutBounds,
18        LayoutContext, LayoutDiagnosticsCollector, LayoutSnapshotMap, measure_node,
19    },
20    modifier::{
21        DrawModifierContent, DrawModifierContext, DrawModifierNode, LayoutModifierNode, Modifier,
22    },
23    px::PxPosition,
24    render_graph::RenderFragment,
25    runtime::configure_layout_node,
26    tessera,
27};
28
29#[derive(Clone, Copy, PartialEq)]
30pub(crate) struct ChildMeasure {
31    pub constraint: Constraint,
32    pub size: ComputedData,
33    pub consistent: bool,
34    pub consumed_insets: crate::Insets,
35}
36
37pub(crate) struct ChildProbe {
38    consumed_insets: crate::Insets,
39    constraint: Constraint,
40    size: ComputedData,
41}
42
43/// Input for a pure layout policy.
44pub struct LayoutInput<'a> {
45    tree: &'a ComponentNodeTree,
46    parent_constraint: ParentConstraint<'a>,
47    children_ids: &'a [crate::NodeId],
48    layout_ctx: Option<&'a LayoutContext<'a>>,
49    measured_children: HashMap<crate::NodeId, ChildMeasure>,
50    probes: HashMap<crate::NodeId, ChildProbe>,
51    intrinsic_dependencies: bool,
52}
53
54/// A direct child layout node available during a measure pass.
55#[derive(Clone, Copy)]
56pub struct LayoutChild {
57    node_id: crate::NodeId,
58    instance_key: u64,
59    index: usize,
60}
61
62impl PartialEq for LayoutChild {
63    fn eq(&self, other: &Self) -> bool {
64        self.node_id == other.node_id
65    }
66}
67
68impl Eq for LayoutChild {}
69
70impl Hash for LayoutChild {
71    fn hash<H: Hasher>(&self, state: &mut H) {
72        self.node_id.hash(state);
73    }
74}
75
76impl LayoutChild {
77    /// Measures this child under the given constraint.
78    ///
79    /// The child is borrowed by index from the current measure scope so the
80    /// metadata map stays uniquely borrowed for the duration of the call.
81    pub fn measure(
82        &self,
83        scope: &mut MeasureScope<'_>,
84        constraint: &Constraint,
85    ) -> Result<MeasuredChild, MeasurementError> {
86        scope.measure_child(self.index, constraint, true)
87    }
88
89    /// Probes this child under the given constraint.
90    ///
91    /// A later probe or measure of the same child under the same constraint
92    /// reuses this result and does not call into the child again. A different
93    /// constraint is measured normally; only the final [`Self::measure`] is
94    /// recorded for the parent snapshot cache.
95    pub fn probe(
96        &self,
97        scope: &mut MeasureScope<'_>,
98        constraint: &Constraint,
99    ) -> Result<MeasuredChild, MeasurementError> {
100        scope.measure_child(self.index, constraint, false)
101    }
102
103    /// Probes natural content dimensions without publishing layout or
104    /// placements.
105    ///
106    /// Minimum interactive-size padding is excluded. Other layout constraints
107    /// and content padding still apply. Use this to choose shared visual
108    /// dimensions before measuring the children normally for interaction.
109    /// Results and metadata are isolated from the normal layout cache.
110    pub fn probe_intrinsic(
111        &self,
112        scope: &mut MeasureScope<'_>,
113        constraint: &Constraint,
114    ) -> Result<MeasuredChild, MeasurementError> {
115        *scope.intrinsic_dependencies = true;
116        if scope.is_intrinsic() {
117            return scope.measure_child(self.index, constraint, false);
118        }
119        let mut metadatas = ComponentNodeMetaDatas::new();
120        let mut snapshots = LayoutSnapshotMap::default();
121        let mut diagnostics = LayoutDiagnosticsCollector::default();
122        let consumed_insets = std::cell::Cell::new(
123            scope
124                .layout_ctx
125                .map_or_else(Default::default, |context| context.consumed_insets.get()),
126        );
127        let context = LayoutContext::intrinsic_probe(
128            &consumed_insets,
129            &mut snapshots,
130            &mut diagnostics,
131            scope.layout_ctx,
132        );
133        let size = measure_node(
134            self.node_id,
135            constraint,
136            scope.tree,
137            &mut metadatas,
138            Some(&context),
139        )?;
140        Ok(MeasuredChild {
141            instance_key: self.instance_key,
142            width: size.width,
143            height: size.height,
144        })
145    }
146
147    /// Reads a typed parent-data payload from this direct child layout node.
148    pub fn parent_data<T>(&self, scope: &MeasureScope<'_>) -> Option<T>
149    where
150        T: Clone + Send + Sync + 'static,
151    {
152        let node = scope.tree.get(self.node_id)?;
153        node.get().modifier.parent_data()
154    }
155
156    pub(crate) const fn instance_key(&self) -> u64 {
157        self.instance_key
158    }
159}
160
161/// A measured child returned from [`LayoutChild::measure`].
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
163pub struct MeasuredChild {
164    instance_key: u64,
165    /// The measured width of the child.
166    pub width: Px,
167    /// The measured height of the child.
168    pub height: Px,
169}
170
171impl MeasuredChild {
172    /// Returns the measured size for this child.
173    pub const fn size(&self) -> ComputedData {
174        ComputedData {
175            width: self.width,
176            height: self.height,
177        }
178    }
179
180    pub(crate) const fn instance_key(&self) -> u64 {
181        self.instance_key
182    }
183}
184
185/// A direct child available during a placement-only pass.
186#[derive(Clone, Copy)]
187pub struct PlacementChild<'a> {
188    node_id: crate::NodeId,
189    instance_key: u64,
190    tree: &'a ComponentNodeTree,
191    size: ComputedData,
192}
193
194impl<'a> PlacementChild<'a> {
195    /// Returns the cached measured size from the preceding measure pass.
196    pub const fn size(&self) -> ComputedData {
197        self.size
198    }
199
200    /// Reads a typed parent-data payload from the child.
201    pub fn parent_data<T>(&self) -> Option<T>
202    where
203        T: Clone + Send + Sync + 'static,
204    {
205        let node = self.tree.get(self.node_id)?;
206        node.get().modifier.parent_data()
207    }
208
209    pub(crate) const fn instance_key(&self) -> u64 {
210        self.instance_key
211    }
212}
213
214/// Shared measurement helpers available during a layout pass.
215pub struct MeasureScope<'a> {
216    tree: &'a ComponentNodeTree,
217    metadatas: &'a mut ComponentNodeMetaDatas,
218    layout_ctx: Option<&'a LayoutContext<'a>>,
219    measured_children: &'a mut HashMap<crate::NodeId, ChildMeasure>,
220    probes: &'a mut HashMap<crate::NodeId, ChildProbe>,
221    intrinsic_dependencies: &'a mut bool,
222    parent_constraint: ParentConstraint<'a>,
223    children_ids: &'a [crate::NodeId],
224}
225
226impl<'a> MeasureScope<'a> {
227    pub(crate) fn new(
228        tree: &'a ComponentNodeTree,
229        metadatas: &'a mut ComponentNodeMetaDatas,
230        layout_ctx: Option<&'a LayoutContext<'a>>,
231        measured_children: &'a mut HashMap<crate::NodeId, ChildMeasure>,
232        probes: &'a mut HashMap<crate::NodeId, ChildProbe>,
233        intrinsic_dependencies: &'a mut bool,
234        parent_constraint: ParentConstraint<'a>,
235        children_ids: &'a [crate::NodeId],
236    ) -> Self {
237        Self {
238            tree,
239            metadatas,
240            layout_ctx,
241            measured_children,
242            probes,
243            intrinsic_dependencies,
244            parent_constraint,
245            children_ids,
246        }
247    }
248
249    /// Returns the inherited constraint.
250    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
251        self.parent_constraint
252    }
253
254    /// Calculates child positions without affecting measured sizes or
255    /// constraints.
256    ///
257    /// State read here invalidates placement instead of measurement. Implement
258    /// [`LayoutModifierNode::place_children`] to update those positions using
259    /// cached child sizes. Keep size and constraint calculations outside this
260    /// closure.
261    pub fn placement<R>(&self, calculate: impl FnOnce() -> R) -> R {
262        let _phase = crate::runtime::push_phase(crate::runtime::RuntimePhase::Place);
263        calculate()
264    }
265
266    /// Returns whether this pass is probing natural content dimensions.
267    pub fn is_intrinsic(&self) -> bool {
268        self.layout_ctx.is_some_and(|context| context.intrinsic)
269    }
270
271    /// Returns copyable child identities. Measurement borrows this scope by
272    /// child index so metadata stays uniquely borrowed for each call.
273    pub fn children(&self) -> Vec<LayoutChild> {
274        self.children_ids
275            .iter()
276            .enumerate()
277            .map(|(index, &node_id)| {
278                let instance_key = self
279                    .tree
280                    .get(node_id)
281                    .unwrap_or_else(|| {
282                        let owner = crate::runtime::current_node_id();
283                        let ancestry = owner
284                            .into_iter()
285                            .flat_map(|id| id.ancestors(self.tree))
286                            .map(|id| (id, self.tree[id].get().fn_name))
287                            .collect::<Vec<_>>();
288                        panic!(
289                            "Direct child layout node must exist: child={node_id:?}, owner={owner:?}, ancestry={ancestry:?}"
290                        )
291                    })
292                    .get()
293                    .instance_key;
294                LayoutChild {
295                    node_id,
296                    instance_key,
297                    index,
298                }
299            })
300            .collect()
301    }
302
303    pub(crate) fn measure_at(
304        &mut self,
305        index: usize,
306        constraint: &Constraint,
307    ) -> Result<MeasuredChild, MeasurementError> {
308        self.measure_child(index, constraint, true)
309    }
310
311    fn measure_child(
312        &mut self,
313        index: usize,
314        constraint: &Constraint,
315        record: bool,
316    ) -> Result<MeasuredChild, MeasurementError> {
317        let node_id = *self
318            .children_ids
319            .get(index)
320            .expect("Layout child index must refer to a direct child");
321        let instance_key = self
322            .tree
323            .get(node_id)
324            .expect("Direct child layout node must exist")
325            .get()
326            .instance_key;
327
328        let consumed_insets = self
329            .layout_ctx
330            .map_or_else(Default::default, |context| context.consumed_insets.get());
331        if let Some(probe) = self.probes.get(&node_id) {
332            if probe.constraint == *constraint && probe.consumed_insets == consumed_insets {
333                let size = probe.size;
334                if record {
335                    self.record_measure(node_id, *constraint, size);
336                }
337                return Ok(MeasuredChild {
338                    instance_key,
339                    width: size.width,
340                    height: size.height,
341                });
342            }
343        }
344
345        let size = measure_node(
346            node_id,
347            constraint,
348            self.tree,
349            self.metadatas,
350            self.layout_ctx,
351        )?;
352        self.probes.insert(
353            node_id,
354            ChildProbe {
355                consumed_insets,
356                constraint: *constraint,
357                size,
358            },
359        );
360        if record {
361            self.record_measure(node_id, *constraint, size);
362        }
363        Ok(MeasuredChild {
364            instance_key,
365            width: size.width,
366            height: size.height,
367        })
368    }
369
370    fn record_measure(
371        &mut self,
372        node_id: crate::NodeId,
373        constraint: Constraint,
374        size: ComputedData,
375    ) {
376        let consumed_insets = self
377            .layout_ctx
378            .map_or_else(Default::default, |context| context.consumed_insets.get());
379        if let Some(entry) = self.measured_children.get_mut(&node_id) {
380            let consistent = entry.consistent
381                && entry.constraint == constraint
382                && entry.size == size
383                && entry.consumed_insets == consumed_insets;
384            entry.constraint = constraint;
385            entry.size = size;
386            entry.consistent = consistent;
387            entry.consumed_insets = consumed_insets;
388        } else {
389            self.measured_children.insert(
390                node_id,
391                ChildMeasure {
392                    consumed_insets,
393                    constraint,
394                    size,
395                    consistent: true,
396                },
397            );
398        }
399    }
400}
401
402/// Shared placement helpers available during a cached placement pass.
403pub struct PlacementScope<'a> {
404    tree: &'a ComponentNodeTree,
405    parent_constraint: ParentConstraint<'a>,
406    children_ids: &'a [crate::NodeId],
407    child_sizes: &'a HashMap<crate::NodeId, ComputedData>,
408    size: ComputedData,
409}
410
411impl<'a> PlacementScope<'a> {
412    pub(crate) fn new(
413        tree: &'a ComponentNodeTree,
414        parent_constraint: ParentConstraint<'a>,
415        children_ids: &'a [crate::NodeId],
416        child_sizes: &'a HashMap<crate::NodeId, ComputedData>,
417        size: ComputedData,
418    ) -> Self {
419        Self {
420            tree,
421            parent_constraint,
422            children_ids,
423            child_sizes,
424            size,
425        }
426    }
427
428    /// Returns the inherited constraint.
429    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
430        self.parent_constraint
431    }
432
433    /// Returns the direct child layout nodes of the current node.
434    pub fn children(&self) -> Vec<PlacementChild<'_>> {
435        self.children_ids
436            .iter()
437            .map(|&node_id| {
438                let node = self
439                    .tree
440                    .get(node_id)
441                    .expect("Direct child layout node must exist");
442                PlacementChild {
443                    node_id,
444                    instance_key: node.get().instance_key,
445                    tree: self.tree,
446                    size: self
447                        .child_sizes
448                        .get(&node_id)
449                        .copied()
450                        .expect("Placement child size must exist for a direct child layout node"),
451                }
452            })
453            .collect()
454    }
455
456    /// Returns the cached size of the current node.
457    pub const fn size(&self) -> ComputedData {
458        self.size
459    }
460}
461
462impl<'a> LayoutInput<'a> {
463    /// Returns window inset space already occupied by enclosing layout modifiers.
464    pub fn consumed_window_insets(&self) -> crate::Insets {
465        self.layout_ctx
466            .map_or_else(Default::default, |context| context.consumed_insets.get())
467    }
468
469    /// Measures nested content while marking the supplied edge lengths as occupied.
470    /// The enclosing consumption is restored before returning.
471    pub fn with_consumed_window_insets<R>(
472        &self,
473        insets: crate::Insets,
474        measure: impl FnOnce() -> R,
475    ) -> R {
476        if let Some(context) = self.layout_ctx {
477            context.with_consumed_insets(self.consumed_window_insets().union(insets), measure)
478        } else {
479            measure()
480        }
481    }
482
483    pub(crate) fn new(
484        tree: &'a ComponentNodeTree,
485        parent_constraint: ParentConstraint<'a>,
486        children_ids: &'a [crate::NodeId],
487        layout_ctx: Option<&'a LayoutContext<'a>>,
488    ) -> Self {
489        Self {
490            tree,
491            parent_constraint,
492            children_ids,
493            layout_ctx,
494            measured_children: HashMap::new(),
495            probes: HashMap::new(),
496            intrinsic_dependencies: false,
497        }
498    }
499
500    /// Returns the inherited constraint.
501    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
502        self.parent_constraint
503    }
504
505    /// Returns whether minimum interactive-size padding should be excluded.
506    pub fn is_intrinsic(&self) -> bool {
507        self.layout_ctx.is_some_and(|context| context.intrinsic)
508    }
509
510    pub(crate) fn take_measured_children(&mut self) -> HashMap<crate::NodeId, ChildMeasure> {
511        std::mem::take(&mut self.measured_children)
512    }
513
514    pub(crate) fn has_intrinsic_dependencies(&self) -> bool {
515        self.intrinsic_dependencies
516    }
517
518    pub(crate) fn extend_measured_children(
519        &mut self,
520        children: HashMap<crate::NodeId, ChildMeasure>,
521    ) {
522        for (child_id, measurement) in children {
523            if let Some(entry) = self.measured_children.get_mut(&child_id) {
524                let consistent = entry.consistent
525                    && entry.constraint == measurement.constraint
526                    && entry.size == measurement.size
527                    && entry.consumed_insets == measurement.consumed_insets
528                    && measurement.consistent;
529                entry.constraint = measurement.constraint;
530                entry.size = measurement.size;
531                entry.consumed_insets = measurement.consumed_insets;
532                entry.consistent = consistent;
533            } else {
534                self.measured_children.insert(child_id, measurement);
535            }
536        }
537    }
538
539    pub(crate) fn measure_scope<'s>(
540        &'s mut self,
541        metadatas: &'s mut ComponentNodeMetaDatas,
542    ) -> MeasureScope<'s> {
543        MeasureScope::new(
544            self.tree,
545            metadatas,
546            self.layout_ctx,
547            &mut self.measured_children,
548            &mut self.probes,
549            &mut self.intrinsic_dependencies,
550            self.parent_constraint,
551            self.children_ids,
552        )
553    }
554
555    /// Returns the direct child layout nodes of the current node.
556    pub fn children(&self) -> Vec<LayoutChild> {
557        self.children_ids
558            .iter()
559            .enumerate()
560            .map(|(index, &node_id)| {
561                let instance_key = self
562                    .tree
563                    .get(node_id)
564                    .expect("Direct child layout node must exist")
565                    .get()
566                    .instance_key;
567                LayoutChild {
568                    node_id,
569                    instance_key,
570                    index,
571                }
572            })
573            .collect()
574    }
575}
576
577/// Cached output from pure layout.
578#[derive(Clone)]
579pub struct LayoutResult {
580    /// The computed size for the node.
581    pub size: ComputedData,
582    /// Child placements keyed by instance_key.
583    pub placements: Vec<(u64, PxPosition)>,
584}
585
586impl LayoutResult {
587    /// Creates a layout result for the current node.
588    pub const fn new(size: ComputedData) -> Self {
589        Self {
590            size,
591            placements: Vec::new(),
592        }
593    }
594
595    /// Places a child relative to the current node origin.
596    pub fn place_child<T>(&mut self, child: T, position: PxPosition)
597    where
598        T: LayoutPlacementTarget,
599    {
600        self.place(child.instance_key(), position);
601    }
602
603    /// Places a direct child by its `instance_key`.
604    pub fn place(&mut self, instance_key: u64, position: PxPosition) {
605        self.placements.push((instance_key, position));
606    }
607
608    /// Replaces the computed size while preserving recorded placements.
609    pub const fn with_size(mut self, size: ComputedData) -> Self {
610        self.size = size;
611        self
612    }
613
614    /// Consumes the result and returns only the recorded placements.
615    pub fn into_placements(self) -> Vec<(u64, PxPosition)> {
616        self.placements
617    }
618
619    /// Consumes the result and wraps the recorded placements.
620    pub fn into_placement_result(self) -> PlacementResult {
621        PlacementResult {
622            placements: self.placements,
623        }
624    }
625}
626
627/// Child placements produced by a measure pass, relative to the node origin.
628#[derive(Clone, Debug, Default, PartialEq)]
629pub struct PlacementResult {
630    placements: Vec<(u64, PxPosition)>,
631}
632
633impl PlacementResult {
634    /// Creates an empty placement result.
635    pub const fn new() -> Self {
636        Self {
637            placements: Vec::new(),
638        }
639    }
640
641    /// Wraps an existing list of placements.
642    pub fn from_placements(placements: Vec<(u64, PxPosition)>) -> Self {
643        Self { placements }
644    }
645
646    /// Records a placement for a direct child `instance_key`.
647    pub fn place(&mut self, instance_key: u64, position: PxPosition) {
648        self.placements.push((instance_key, position));
649    }
650
651    /// Returns whether nothing was placed.
652    pub fn is_empty(&self) -> bool {
653        self.placements.is_empty()
654    }
655
656    /// Returns the number of recorded placements.
657    pub fn len(&self) -> usize {
658        self.placements.len()
659    }
660
661    /// Borrows the recorded placements.
662    pub fn items(&self) -> &[(u64, PxPosition)] {
663        &self.placements
664    }
665
666    /// Iterates over the recorded placements.
667    pub fn iter(&self) -> std::slice::Iter<'_, (u64, PxPosition)> {
668        self.placements.iter()
669    }
670
671    /// Consumes the result and returns the recorded placements.
672    pub fn into_placements(self) -> Vec<(u64, PxPosition)> {
673        self.placements
674    }
675}
676
677impl Default for LayoutResult {
678    fn default() -> Self {
679        Self::new(ComputedData::ZERO)
680    }
681}
682
683/// A direct child handle that can be placed by [`LayoutResult`].
684pub trait LayoutPlacementTarget: Copy {
685    #[doc(hidden)]
686    fn instance_key(&self) -> u64;
687}
688
689impl LayoutPlacementTarget for LayoutChild {
690    fn instance_key(&self) -> u64 {
691        self.instance_key()
692    }
693}
694
695impl LayoutPlacementTarget for MeasuredChild {
696    fn instance_key(&self) -> u64 {
697        self.instance_key()
698    }
699}
700
701impl LayoutPlacementTarget for PlacementChild<'_> {
702    fn instance_key(&self) -> u64 {
703        self.instance_key()
704    }
705}
706
707/// Input for a render record pass.
708pub struct RenderInput<'a> {
709    current_node_id: crate::NodeId,
710    metadatas: &'a mut ComponentNodeMetaDatas,
711    pub(crate) draw_bounds: DrawLayoutBounds,
712}
713
714impl<'a> RenderInput<'a> {
715    pub(crate) fn new(
716        current_node_id: crate::NodeId,
717        metadatas: &'a mut ComponentNodeMetaDatas,
718    ) -> Self {
719        let size = metadatas
720            .get(&current_node_id)
721            .and_then(|metadata| metadata.computed_data)
722            .unwrap_or(ComputedData::ZERO);
723        Self {
724            current_node_id,
725            metadatas,
726            draw_bounds: DrawLayoutBounds {
727                position: PxPosition::ZERO,
728                size,
729            },
730        }
731    }
732
733    /// Returns a mutable render metadata handle for the current node.
734    pub fn metadata_mut(&mut self) -> RenderMetadataMut<'_> {
735        let metadata = self
736            .metadatas
737            .get_mut(&self.current_node_id)
738            .expect("Metadata for current node must exist during record");
739        RenderMetadataMut {
740            metadata,
741            draw_bounds: self.draw_bounds,
742        }
743    }
744
745    pub(crate) fn recorded_op_count(&self) -> usize {
746        self.metadatas
747            .get(&self.current_node_id)
748            .expect("record metadata")
749            .fragment
750            .ops()
751            .len()
752    }
753
754    pub(crate) fn draw_layers(&self) -> &[Option<DrawLayoutBounds>] {
755        self.metadatas
756            .get(&self.current_node_id)
757            .expect("record metadata")
758            .draw_layout
759            .layers
760            .as_deref()
761            .unwrap_or_default()
762    }
763
764    pub(crate) fn finish_draw_range(&mut self, start: usize, bounds: DrawLayoutBounds) {
765        let metadata = self
766            .metadatas
767            .get_mut(&self.current_node_id)
768            .expect("record metadata");
769        for op in metadata.fragment.ops_mut().iter_mut().skip(start) {
770            op.position_override =
771                Some(bounds.position + op.position_override.unwrap_or(PxPosition::ZERO));
772            op.size_override.get_or_insert(bounds.size.into());
773        }
774    }
775}
776
777/// Mutable render metadata available during the record pass.
778pub struct RenderMetadataMut<'a> {
779    metadata: &'a mut ComponentNodeMetaData,
780    draw_bounds: DrawLayoutBounds,
781}
782
783impl RenderMetadataMut<'_> {
784    /// Returns the measured size at the current drawing layer.
785    pub fn computed_data(&self) -> Option<ComputedData> {
786        self.metadata.computed_data.map(|_| self.draw_bounds.size)
787    }
788
789    /// Returns the render fragment for the current node.
790    pub fn fragment_mut(&mut self) -> &mut RenderFragment {
791        self.metadata.fragment_mut()
792    }
793
794    /// Enables or disables child clipping for the current node.
795    pub fn set_clips_children(&mut self, clips_children: bool) {
796        self.metadata.clips_children = clips_children;
797        if clips_children
798            && self.metadata.clip_region().is_none()
799            && (self.draw_bounds.position != PxPosition::ZERO
800                || self.metadata.computed_data != Some(self.draw_bounds.size))
801        {
802            self.set_clip_shape(crate::ClipShape::RoundedRectangle {
803                radii: [0.0; 4],
804                exponents: [2.0; 4],
805            });
806        }
807    }
808
809    /// Clips the node and its descendants to a shape within its bounds.
810    pub fn set_clip_shape(&mut self, shape: crate::ClipShape) {
811        self.metadata.clips_children = true;
812        let size = self
813            .computed_data()
814            .expect("shape clipping requires measured bounds");
815        self.metadata.set_clip_region(Some(crate::ClipRegion {
816            position: self.draw_bounds.position,
817            size: [size.width.to_f32(), size.height.to_f32()],
818            shape,
819        }));
820    }
821
822    /// Clips content to a fractional shape relative to the current drawing
823    /// layer.
824    pub fn set_clip_region(&mut self, mut region: crate::ClipRegion) {
825        self.metadata.clips_children = true;
826        region.position = region.position + self.draw_bounds.position;
827        self.metadata.set_clip_region(Some(region));
828    }
829
830    /// Multiplies the current node opacity by the provided factor.
831    pub fn multiply_opacity(&mut self, opacity: f32) {
832        self.metadata.opacity *= opacity;
833    }
834}
835
836/// Type-erased layout node used by the runtime.
837#[doc(hidden)]
838pub trait LayoutModifierDyn: Send + Sync {
839    /// Returns a typed reference for downcasting.
840    fn as_any(&self) -> &dyn Any;
841    /// Measures layout using a type-erased node.
842    fn measure_dyn(&self, scope: &mut MeasureScope<'_>) -> Result<LayoutResult, MeasurementError>;
843    /// Recomputes child placements using cached child measurements.
844    fn place_children_dyn(
845        &self,
846        scope: &PlacementScope<'_>,
847        inner: &PlacementResult,
848    ) -> Option<PlacementResult>;
849    /// Compares two type-erased nodes for equality.
850    fn dyn_eq(&self, other: &dyn LayoutModifierDyn) -> bool;
851    /// Compares two type-erased nodes for measurement-relevant equality.
852    fn dyn_measure_eq(&self, other: &dyn LayoutModifierDyn) -> bool;
853    /// Compares two type-erased nodes for placement-relevant equality.
854    fn dyn_placement_eq(&self, other: &dyn LayoutModifierDyn) -> bool;
855    /// Clones the type-erased node.
856    fn clone_box(&self) -> Box<dyn LayoutModifierDyn>;
857}
858
859impl<T> LayoutModifierDyn for T
860where
861    T: LayoutModifierNode + Clone + PartialEq,
862{
863    fn as_any(&self) -> &dyn Any {
864        self
865    }
866
867    fn measure_dyn(&self, scope: &mut MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
868        crate::work::record_policy::<T>(if scope.is_intrinsic() {
869            crate::WorkPhase::IntrinsicMeasure
870        } else {
871            crate::WorkPhase::Measure
872        });
873        LayoutModifierNode::measure(self, scope)
874    }
875
876    fn place_children_dyn(
877        &self,
878        scope: &PlacementScope<'_>,
879        inner: &PlacementResult,
880    ) -> Option<PlacementResult> {
881        crate::work::record_policy::<T>(crate::WorkPhase::Place);
882        LayoutModifierNode::place_children(self, scope, inner)
883    }
884
885    fn dyn_eq(&self, other: &dyn LayoutModifierDyn) -> bool {
886        other
887            .as_any()
888            .downcast_ref::<T>()
889            .is_some_and(|other| self == other)
890    }
891
892    fn dyn_measure_eq(&self, other: &dyn LayoutModifierDyn) -> bool {
893        other
894            .as_any()
895            .downcast_ref::<T>()
896            .is_some_and(|other| LayoutModifierNode::measure_eq(self, other))
897    }
898
899    fn dyn_placement_eq(&self, other: &dyn LayoutModifierDyn) -> bool {
900        other
901            .as_any()
902            .downcast_ref::<T>()
903            .is_some_and(|other| LayoutModifierNode::placement_eq(self, other))
904    }
905
906    fn clone_box(&self) -> Box<dyn LayoutModifierDyn> {
907        Box::new(self.clone())
908    }
909}
910
911impl Clone for Box<dyn LayoutModifierDyn> {
912    fn clone(&self) -> Self {
913        self.clone_box()
914    }
915}
916
917/// Type-erased layout node handle consumed by [`layout`].
918///
919/// Most callers do not construct this type directly. Instead, pass any
920/// [`LayoutModifierNode`] to the `policy(...)` builder method and rely on the
921/// `From<T>` conversion.
922#[derive(Clone, Default)]
923pub struct LayoutModifierHandle {
924    node: Option<Arc<dyn LayoutModifierDyn>>,
925}
926
927impl LayoutModifierHandle {
928    pub(crate) fn as_policy(&self) -> &dyn LayoutModifierDyn {
929        self.node.as_deref().unwrap_or(&DefaultLayoutNode)
930    }
931}
932
933impl PartialEq for LayoutModifierHandle {
934    fn eq(&self, other: &Self) -> bool {
935        self.as_policy().dyn_eq(other.as_policy())
936    }
937}
938
939impl<S> From<S> for LayoutModifierHandle
940where
941    S: LayoutModifierNode + Clone + PartialEq,
942{
943    fn from(node: S) -> Self {
944        Self {
945            node: Some(Arc::new(node)),
946        }
947    }
948}
949
950/// Type-erased draw modifier handle consumed by [`layout`].
951///
952/// Most callers do not construct this type directly. Instead, pass any
953/// [`DrawModifierNode`] to the `draw(...)` builder method and rely on the
954/// `From<T>` conversion.
955#[derive(Clone)]
956pub struct DrawModifierHandle {
957    node: Box<dyn ErasedDrawModifierNode>,
958}
959
960impl DrawModifierHandle {
961    pub(crate) fn into_node(self) -> Box<dyn DrawModifierNode> {
962        self.node.into_node()
963    }
964}
965
966impl Default for DrawModifierHandle {
967    fn default() -> Self {
968        Self {
969            node: Box::new(NoopDrawModifier),
970        }
971    }
972}
973
974impl PartialEq for DrawModifierHandle {
975    fn eq(&self, other: &Self) -> bool {
976        self.node.dyn_eq(other.node.as_ref())
977    }
978}
979
980impl<N> From<N> for DrawModifierHandle
981where
982    N: DrawModifierNode + Clone + PartialEq,
983{
984    fn from(node: N) -> Self {
985        Self {
986            node: Box::new(node),
987        }
988    }
989}
990
991/// Type-erased draw modifier node used by [`DrawModifierHandle`].
992trait ErasedDrawModifierNode: Send + Sync {
993    fn as_any(&self) -> &dyn Any;
994    fn into_node(self: Box<Self>) -> Box<dyn DrawModifierNode>;
995    fn dyn_eq(&self, other: &dyn ErasedDrawModifierNode) -> bool;
996    fn clone_box(&self) -> Box<dyn ErasedDrawModifierNode>;
997}
998
999impl<N> ErasedDrawModifierNode for N
1000where
1001    N: DrawModifierNode + Clone + PartialEq,
1002{
1003    fn as_any(&self) -> &dyn Any {
1004        self
1005    }
1006
1007    fn into_node(self: Box<Self>) -> Box<dyn DrawModifierNode> {
1008        self
1009    }
1010
1011    fn dyn_eq(&self, other: &dyn ErasedDrawModifierNode) -> bool {
1012        other
1013            .as_any()
1014            .downcast_ref::<N>()
1015            .is_some_and(|other| self == other)
1016    }
1017
1018    fn clone_box(&self) -> Box<dyn ErasedDrawModifierNode> {
1019        Box::new(self.clone())
1020    }
1021}
1022
1023impl Clone for Box<dyn ErasedDrawModifierNode> {
1024    fn clone(&self) -> Self {
1025        self.clone_box()
1026    }
1027}
1028
1029/// # layout
1030///
1031/// Emit an explicit layout node with a layout policy, draw modifier, modifier
1032/// chain, and optional child slot.
1033///
1034/// ## Usage
1035///
1036/// Build framework or internal components that need to define explicit layout
1037/// and rendering behavior inside a tessera composition boundary.
1038///
1039/// ## Parameters
1040///
1041/// - `policy` - the unified layout node used for measuring and placing the
1042///   emitted layout node, defaulting to [`DefaultLayoutNode`]
1043/// - `draw` - draw modifier used to record draw and compute commands for the
1044///   emitted layout node, defaulting to [`NoopDrawModifier`]
1045/// - `modifier` - node-local modifier chain attached before child content is
1046///   emitted, defaulting to an empty [`Modifier`]
1047/// - `child` - optional child slot rendered as the emitted node's content
1048///   subtree
1049///
1050/// ## Examples
1051/// ```
1052/// use tessera_ui::{
1053///     Modifier, NoopDrawModifier, RenderSlot,
1054///     layout::{DefaultLayoutNode, layout},
1055/// };
1056///
1057/// #[tessera_ui::tessera]
1058/// fn primitive_example(modifier: Modifier, child: RenderSlot) {
1059///     layout()
1060///         .policy(DefaultLayoutNode)
1061///         .draw(NoopDrawModifier)
1062///         .modifier(modifier)
1063///         .child(move || child.render());
1064/// }
1065/// ```
1066#[tessera(crate, layout)]
1067pub fn layout(
1068    #[prop(into)] policy: Option<LayoutModifierHandle>,
1069    #[prop(into)] draw: Option<DrawModifierHandle>,
1070    modifier: Option<Modifier>,
1071    child: Option<RenderSlot>,
1072) {
1073    configure_layout_node(
1074        policy.unwrap_or_default(),
1075        draw.unwrap_or_default(),
1076        modifier.unwrap_or_default(),
1077    );
1078    if let Some(child) = child {
1079        child.render();
1080    }
1081}
1082
1083/// Default layout node that stacks children at (0,0) and uses the bounding
1084/// size.
1085#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
1086pub struct DefaultLayoutNode;
1087
1088impl LayoutModifierNode for DefaultLayoutNode {
1089    fn measure(&self, scope: &mut MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
1090        let child_count = scope.children().len();
1091        if child_count == 0 {
1092            return Ok(LayoutResult::new(ComputedData::min_from_constraint(
1093                scope.parent_constraint().as_ref(),
1094            )));
1095        }
1096
1097        let mut result = LayoutResult::new(ComputedData::ZERO);
1098        let mut final_width = Px(0);
1099        let mut final_height = Px(0);
1100        let constraint = *scope.parent_constraint().as_ref();
1101        for index in 0..child_count {
1102            let measurement = scope.measure_at(index, &constraint)?;
1103            result.place_child(measurement, PxPosition::ZERO);
1104            let size = measurement.size();
1105            final_width = final_width.max(size.width);
1106            final_height = final_height.max(size.height);
1107        }
1108
1109        result.size = ComputedData {
1110            width: final_width,
1111            height: final_height,
1112        };
1113        Ok(result)
1114    }
1115
1116    fn place_children(
1117        &self,
1118        scope: &PlacementScope<'_>,
1119        _inner: &PlacementResult,
1120    ) -> Option<PlacementResult> {
1121        // Children are stacked at the origin, so placements only depend on the
1122        // cached child identities, not on their measured sizes.
1123        let mut result = PlacementResult::new();
1124        for child in scope.children() {
1125            result.place(child.instance_key(), PxPosition::ZERO);
1126        }
1127        Some(result)
1128    }
1129}
1130
1131/// Default draw modifier that records the wrapped content unchanged.
1132#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
1133pub struct NoopDrawModifier;
1134
1135impl DrawModifierNode for NoopDrawModifier {
1136    fn draw(&self, ctx: &mut DrawModifierContext<'_, '_>, content: &mut dyn DrawModifierContent) {
1137        content.draw(ctx.render_input);
1138    }
1139}