tessera_ui/
layout.rs

1//! Layout policies and measurement.
2
3use std::{
4    any::{Any, TypeId},
5    cell::RefCell,
6    collections::HashMap,
7    sync::Arc,
8};
9
10use parking_lot::RwLock;
11
12use crate::{
13    ComputeResourceManager, ComputedData, Constraint, MeasurementError, ParentConstraint, Px,
14    RenderSlot,
15    component_tree::{
16        ComponentNodeMetaDatas, ComponentNodeTree, LayoutContext, measure_node, measure_nodes,
17    },
18    modifier::{Modifier, ParentDataMap},
19    prop::Prop,
20    px::PxPosition,
21    render_graph::RenderFragment,
22    runtime::TesseraRuntime,
23    tessera,
24};
25
26#[derive(Clone, Copy)]
27pub(crate) struct ChildMeasure {
28    pub constraint: Constraint,
29    pub size: ComputedData,
30    pub consistent: bool,
31}
32
33/// Input for a pure layout policy.
34pub struct LayoutInput<'a> {
35    tree: &'a ComponentNodeTree,
36    parent_constraint: ParentConstraint<'a>,
37    children_ids: &'a [crate::NodeId],
38    metadatas: &'a ComponentNodeMetaDatas,
39    layout_ctx: Option<&'a LayoutContext<'a>>,
40    measured_children: RefCell<HashMap<crate::NodeId, ChildMeasure>>,
41}
42
43impl<'a> LayoutInput<'a> {
44    pub(crate) fn new(
45        tree: &'a ComponentNodeTree,
46        parent_constraint: ParentConstraint<'a>,
47        children_ids: &'a [crate::NodeId],
48        metadatas: &'a ComponentNodeMetaDatas,
49        layout_ctx: Option<&'a LayoutContext<'a>>,
50    ) -> Self {
51        Self {
52            tree,
53            parent_constraint,
54            children_ids,
55            metadatas,
56            layout_ctx,
57            measured_children: RefCell::new(HashMap::new()),
58        }
59    }
60
61    /// Returns the inherited constraint.
62    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
63        self.parent_constraint
64    }
65
66    /// Returns the children node ids of the current node.
67    pub fn children_ids(&self) -> &'a [crate::NodeId] {
68        self.children_ids
69    }
70
71    pub(crate) fn take_measured_children(&self) -> HashMap<crate::NodeId, ChildMeasure> {
72        std::mem::take(&mut *self.measured_children.borrow_mut())
73    }
74
75    pub(crate) fn extend_measured_children(&self, children: HashMap<crate::NodeId, ChildMeasure>) {
76        let mut measured_children = self.measured_children.borrow_mut();
77        for (child_id, measurement) in children {
78            if let Some(entry) = measured_children.get_mut(&child_id) {
79                let consistent = entry.consistent
80                    && entry.constraint == measurement.constraint
81                    && entry.size == measurement.size
82                    && measurement.consistent;
83                entry.constraint = measurement.constraint;
84                entry.size = measurement.size;
85                entry.consistent = consistent;
86            } else {
87                measured_children.insert(child_id, measurement);
88            }
89        }
90    }
91
92    fn record_child_measure(
93        &self,
94        child_id: crate::NodeId,
95        constraint: Constraint,
96        size: ComputedData,
97    ) {
98        let mut measured_children = self.measured_children.borrow_mut();
99        if let Some(entry) = measured_children.get_mut(&child_id) {
100            let consistent =
101                entry.consistent && entry.constraint == constraint && entry.size == size;
102            entry.constraint = constraint;
103            entry.size = size;
104            entry.consistent = consistent;
105        } else {
106            measured_children.insert(
107                child_id,
108                ChildMeasure {
109                    constraint,
110                    size,
111                    consistent: true,
112                },
113            );
114        }
115    }
116
117    /// Measures all specified child nodes under the given constraint.
118    pub fn measure_children(
119        &self,
120        nodes_to_measure: Vec<(crate::NodeId, Constraint)>,
121    ) -> Result<HashMap<crate::NodeId, ComputedData>, MeasurementError> {
122        let constraints: HashMap<crate::NodeId, Constraint> = nodes_to_measure
123            .iter()
124            .map(|(child_id, constraint)| (*child_id, *constraint))
125            .collect();
126        let results = measure_nodes(nodes_to_measure, self.tree, self.metadatas, self.layout_ctx);
127
128        let mut successful_results = HashMap::new();
129        for (child_id, result) in results {
130            match result {
131                Ok(size) => {
132                    if let Some(constraint) = constraints.get(&child_id) {
133                        self.record_child_measure(child_id, *constraint, size);
134                    }
135                    successful_results.insert(child_id, size);
136                }
137                Err(e) => {
138                    return Err(e);
139                }
140            };
141        }
142        Ok(successful_results)
143    }
144
145    /// Measures child nodes without recording them for layout cache keys.
146    pub fn measure_children_untracked(
147        &self,
148        nodes_to_measure: Vec<(crate::NodeId, Constraint)>,
149    ) -> Result<HashMap<crate::NodeId, ComputedData>, MeasurementError> {
150        let results = measure_nodes(nodes_to_measure, self.tree, self.metadatas, self.layout_ctx);
151
152        let mut successful_results = HashMap::new();
153        for (child_id, result) in results {
154            match result {
155                Ok(size) => {
156                    successful_results.insert(child_id, size);
157                }
158                Err(e) => {
159                    return Err(e);
160                }
161            };
162        }
163        Ok(successful_results)
164    }
165
166    /// Measures a single child node under the given constraint.
167    pub fn measure_child(
168        &self,
169        child_id: crate::NodeId,
170        constraint: &Constraint,
171    ) -> Result<ComputedData, MeasurementError> {
172        let size = measure_node(
173            child_id,
174            constraint,
175            self.tree,
176            self.metadatas,
177            self.layout_ctx,
178        )?;
179        self.record_child_measure(child_id, *constraint, size);
180        Ok(size)
181    }
182
183    /// Measures a child node without recording it for layout cache keys.
184    pub fn measure_child_untracked(
185        &self,
186        child_id: crate::NodeId,
187        constraint: &Constraint,
188    ) -> Result<ComputedData, MeasurementError> {
189        measure_node(
190            child_id,
191            constraint,
192            self.tree,
193            self.metadatas,
194            self.layout_ctx,
195        )
196    }
197
198    /// Measures a single child node using this node's inherited constraint.
199    pub fn measure_child_in_parent_constraint(
200        &self,
201        child_id: crate::NodeId,
202    ) -> Result<ComputedData, MeasurementError> {
203        self.measure_child(child_id, self.parent_constraint.as_ref())
204    }
205
206    /// Reads a typed parent-data payload from an immediate child node.
207    pub fn child_parent_data<T>(&self, child_id: crate::NodeId) -> Option<T>
208    where
209        T: Clone + Send + Sync + 'static,
210    {
211        let node = self.tree.get(child_id)?;
212        let mut data: ParentDataMap = HashMap::default();
213        node.get().modifier.apply_parent_data(&mut data);
214        let value = data.get(&TypeId::of::<T>())?;
215        value.downcast_ref::<T>().cloned()
216    }
217}
218
219/// Input for a placement-only pass that reuses cached child measurements.
220pub struct PlacementInput<'a> {
221    tree: &'a ComponentNodeTree,
222    parent_constraint: ParentConstraint<'a>,
223    children_ids: &'a [crate::NodeId],
224    child_sizes: &'a HashMap<crate::NodeId, ComputedData>,
225    size: ComputedData,
226}
227
228impl<'a> PlacementInput<'a> {
229    pub(crate) fn new(
230        tree: &'a ComponentNodeTree,
231        parent_constraint: ParentConstraint<'a>,
232        children_ids: &'a [crate::NodeId],
233        child_sizes: &'a HashMap<crate::NodeId, ComputedData>,
234        size: ComputedData,
235    ) -> Self {
236        Self {
237            tree,
238            parent_constraint,
239            children_ids,
240            child_sizes,
241            size,
242        }
243    }
244
245    /// Returns the inherited constraint.
246    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
247        self.parent_constraint
248    }
249
250    /// Returns the children node ids of the current node.
251    pub fn children_ids(&self) -> &'a [crate::NodeId] {
252        self.children_ids
253    }
254
255    /// Returns the measured size of a direct child from the cached measurement
256    /// pass.
257    pub fn child_size(&self, child_id: crate::NodeId) -> Option<ComputedData> {
258        self.child_sizes.get(&child_id).copied()
259    }
260
261    /// Returns the cached size of the current node.
262    pub const fn size(&self) -> ComputedData {
263        self.size
264    }
265
266    /// Reads a typed parent-data payload from an immediate child node.
267    pub fn child_parent_data<T>(&self, child_id: crate::NodeId) -> Option<T>
268    where
269        T: Clone + Send + Sync + 'static,
270    {
271        let node = self.tree.get(child_id)?;
272        let mut data: ParentDataMap = HashMap::default();
273        node.get().modifier.apply_parent_data(&mut data);
274        let value = data.get(&TypeId::of::<T>())?;
275        value.downcast_ref::<T>().cloned()
276    }
277}
278
279/// Output collected during pure layout.
280pub struct LayoutOutput<'a> {
281    placements: Vec<(u64, PxPosition)>,
282    resolve_instance_key: &'a dyn Fn(crate::NodeId) -> u64,
283}
284
285impl<'a> LayoutOutput<'a> {
286    pub(crate) fn new(resolve_instance_key: &'a dyn Fn(crate::NodeId) -> u64) -> Self {
287        Self {
288            placements: Vec::new(),
289            resolve_instance_key,
290        }
291    }
292
293    /// Sets the relative position of a child node.
294    pub fn place_child(&mut self, child_id: crate::NodeId, position: PxPosition) {
295        let instance_key = (self.resolve_instance_key)(child_id);
296        self.placements.push((instance_key, position));
297    }
298
299    pub(crate) fn place_instance_key(&mut self, instance_key: u64, position: PxPosition) {
300        self.placements.push((instance_key, position));
301    }
302
303    pub(crate) fn finish(self) -> Vec<(u64, PxPosition)> {
304        self.placements
305    }
306}
307
308/// Cached output from pure layout.
309#[derive(Clone)]
310pub struct LayoutResult {
311    /// The computed size for the node.
312    pub size: ComputedData,
313    /// Child placements keyed by instance_key.
314    pub placements: Vec<(u64, PxPosition)>,
315}
316
317/// Input for a render record pass.
318pub struct RenderInput<'a> {
319    current_node_id: crate::NodeId,
320    metadatas: &'a ComponentNodeMetaDatas,
321    /// Shared GPU compute resources for the current frame.
322    pub compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
323    /// GPU device for issuing render-side allocations.
324    pub gpu: &'a wgpu::Device,
325}
326
327impl<'a> RenderInput<'a> {
328    pub(crate) fn new(
329        current_node_id: crate::NodeId,
330        metadatas: &'a ComponentNodeMetaDatas,
331        compute_resource_manager: Arc<RwLock<ComputeResourceManager>>,
332        gpu: &'a wgpu::Device,
333    ) -> Self {
334        Self {
335            current_node_id,
336            metadatas,
337            compute_resource_manager,
338            gpu,
339        }
340    }
341
342    /// Returns a mutable render metadata handle for the current node.
343    pub fn metadata_mut(&self) -> RenderMetadataMut<'_> {
344        let metadata = self
345            .metadatas
346            .get_mut(&self.current_node_id)
347            .expect("Metadata for current node must exist during record");
348        RenderMetadataMut { metadata }
349    }
350}
351
352/// Mutable render metadata available during the record pass.
353pub struct RenderMetadataMut<'a> {
354    metadata: dashmap::mapref::one::RefMut<
355        'a,
356        crate::NodeId,
357        crate::component_tree::ComponentNodeMetaData,
358    >,
359}
360
361impl RenderMetadataMut<'_> {
362    /// Returns the computed size of the current node.
363    pub fn computed_data(&self) -> Option<ComputedData> {
364        self.metadata.computed_data
365    }
366
367    /// Returns the render fragment for the current node.
368    pub fn fragment_mut(&mut self) -> &mut RenderFragment {
369        self.metadata.fragment_mut()
370    }
371
372    /// Enables or disables child clipping for the current node.
373    pub fn set_clips_children(&mut self, clips_children: bool) {
374        self.metadata.clips_children = clips_children;
375    }
376
377    /// Multiplies the current node opacity by the provided factor.
378    pub fn multiply_opacity(&mut self, opacity: f32) {
379        self.metadata.opacity *= opacity;
380    }
381}
382
383/// Pure layout policy for measuring and placing child nodes.
384pub trait LayoutPolicy: Send + Sync + Clone + PartialEq + 'static {
385    /// Computes layout for the current node.
386    fn measure(
387        &self,
388        input: &LayoutInput<'_>,
389        output: &mut LayoutOutput<'_>,
390    ) -> Result<ComputedData, MeasurementError>;
391
392    /// Compares measurement-relevant state for layout dirty tracking.
393    fn measure_eq(&self, other: &Self) -> bool {
394        self == other
395    }
396
397    /// Compares placement-relevant state for layout dirty tracking.
398    fn placement_eq(&self, other: &Self) -> bool {
399        self == other
400    }
401
402    /// Recomputes child placements using cached child measurements.
403    ///
404    /// Returns `true` when the placement pass was handled without
405    /// remeasurement.
406    fn place_children(&self, _input: &PlacementInput<'_>, _output: &mut LayoutOutput<'_>) -> bool {
407        false
408    }
409}
410
411/// Render policy for recording draw and compute commands for the current node.
412pub trait RenderPolicy: Send + Sync + Clone + PartialEq + 'static {
413    /// Records draw and compute commands for the current node.
414    fn record(&self, _input: &RenderInput<'_>) {}
415}
416
417/// Type-erased layout policy used by the runtime.
418#[doc(hidden)]
419pub trait LayoutPolicyDyn: Send + Sync {
420    /// Returns a typed reference for downcasting.
421    fn as_any(&self) -> &dyn Any;
422    /// Measures layout using a type-erased policy.
423    fn measure_dyn(
424        &self,
425        input: &LayoutInput<'_>,
426        output: &mut LayoutOutput<'_>,
427    ) -> Result<ComputedData, MeasurementError>;
428    /// Recomputes child placements using cached child measurements.
429    fn place_children_dyn(&self, input: &PlacementInput<'_>, output: &mut LayoutOutput<'_>)
430    -> bool;
431    /// Compares two type-erased policies for equality.
432    fn dyn_eq(&self, other: &dyn LayoutPolicyDyn) -> bool;
433    /// Compares two type-erased policies for measurement-relevant equality.
434    fn dyn_measure_eq(&self, other: &dyn LayoutPolicyDyn) -> bool;
435    /// Compares two type-erased policies for placement-relevant equality.
436    fn dyn_placement_eq(&self, other: &dyn LayoutPolicyDyn) -> bool;
437    /// Clones the type-erased policy.
438    fn clone_box(&self) -> Box<dyn LayoutPolicyDyn>;
439}
440
441impl<T> LayoutPolicyDyn for T
442where
443    T: LayoutPolicy,
444{
445    fn as_any(&self) -> &dyn Any {
446        self
447    }
448
449    fn measure_dyn(
450        &self,
451        input: &LayoutInput<'_>,
452        output: &mut LayoutOutput<'_>,
453    ) -> Result<ComputedData, MeasurementError> {
454        LayoutPolicy::measure(self, input, output)
455    }
456
457    fn place_children_dyn(
458        &self,
459        input: &PlacementInput<'_>,
460        output: &mut LayoutOutput<'_>,
461    ) -> bool {
462        LayoutPolicy::place_children(self, input, output)
463    }
464
465    fn dyn_eq(&self, other: &dyn LayoutPolicyDyn) -> bool {
466        other
467            .as_any()
468            .downcast_ref::<T>()
469            .is_some_and(|other| self == other)
470    }
471
472    fn dyn_measure_eq(&self, other: &dyn LayoutPolicyDyn) -> bool {
473        other
474            .as_any()
475            .downcast_ref::<T>()
476            .is_some_and(|other| LayoutPolicy::measure_eq(self, other))
477    }
478
479    fn dyn_placement_eq(&self, other: &dyn LayoutPolicyDyn) -> bool {
480        other
481            .as_any()
482            .downcast_ref::<T>()
483            .is_some_and(|other| LayoutPolicy::placement_eq(self, other))
484    }
485
486    fn clone_box(&self) -> Box<dyn LayoutPolicyDyn> {
487        Box::new(self.clone())
488    }
489}
490
491/// Type-erased render policy used by the runtime.
492#[doc(hidden)]
493pub trait RenderPolicyDyn: Send + Sync {
494    /// Returns a typed reference for downcasting.
495    fn as_any(&self) -> &dyn Any;
496    /// Records render commands using a type-erased policy.
497    fn record_dyn(&self, input: &RenderInput<'_>);
498    /// Compares two type-erased policies for equality.
499    fn dyn_eq(&self, other: &dyn RenderPolicyDyn) -> bool;
500    /// Clones the type-erased policy.
501    fn clone_box(&self) -> Box<dyn RenderPolicyDyn>;
502}
503
504impl<T> RenderPolicyDyn for T
505where
506    T: RenderPolicy,
507{
508    fn as_any(&self) -> &dyn Any {
509        self
510    }
511
512    fn record_dyn(&self, input: &RenderInput<'_>) {
513        RenderPolicy::record(self, input);
514    }
515
516    fn dyn_eq(&self, other: &dyn RenderPolicyDyn) -> bool {
517        other
518            .as_any()
519            .downcast_ref::<T>()
520            .is_some_and(|other| self == other)
521    }
522
523    fn clone_box(&self) -> Box<dyn RenderPolicyDyn> {
524        Box::new(self.clone())
525    }
526}
527
528impl Clone for Box<dyn LayoutPolicyDyn> {
529    fn clone(&self) -> Self {
530        self.clone_box()
531    }
532}
533
534impl Clone for Box<dyn RenderPolicyDyn> {
535    fn clone(&self) -> Self {
536        self.clone_box()
537    }
538}
539
540/// Type-erased layout policy handle consumed by [`layout_primitive`].
541///
542/// Most callers do not construct this type directly. Instead, pass any
543/// [`LayoutPolicy`] to the `layout_policy(...)` builder method and rely on the
544/// `From<T>` conversion.
545#[derive(Clone)]
546pub struct LayoutPolicyHandle {
547    policy: Box<dyn LayoutPolicyDyn>,
548}
549
550impl LayoutPolicyHandle {
551    pub(crate) fn into_box(self) -> Box<dyn LayoutPolicyDyn> {
552        self.policy
553    }
554}
555
556impl Default for LayoutPolicyHandle {
557    fn default() -> Self {
558        Self {
559            policy: Box::new(DefaultLayoutPolicy),
560        }
561    }
562}
563
564impl PartialEq for LayoutPolicyHandle {
565    fn eq(&self, other: &Self) -> bool {
566        self.policy.dyn_eq(other.policy.as_ref())
567    }
568}
569
570impl Prop for LayoutPolicyHandle {
571    fn prop_eq(&self, other: &Self) -> bool {
572        self == other
573    }
574}
575
576impl<S> From<S> for LayoutPolicyHandle
577where
578    S: LayoutPolicy,
579{
580    fn from(policy: S) -> Self {
581        Self {
582            policy: Box::new(policy),
583        }
584    }
585}
586
587/// Type-erased render policy handle consumed by [`layout_primitive`].
588///
589/// Most callers do not construct this type directly. Instead, pass any
590/// [`RenderPolicy`] to the `render_policy(...)` builder method and rely on the
591/// `From<T>` conversion.
592#[derive(Clone)]
593pub struct RenderPolicyHandle {
594    policy: Box<dyn RenderPolicyDyn>,
595}
596
597impl RenderPolicyHandle {
598    pub(crate) fn into_box(self) -> Box<dyn RenderPolicyDyn> {
599        self.policy
600    }
601}
602
603impl Default for RenderPolicyHandle {
604    fn default() -> Self {
605        Self {
606            policy: Box::new(NoopRenderPolicy),
607        }
608    }
609}
610
611impl PartialEq for RenderPolicyHandle {
612    fn eq(&self, other: &Self) -> bool {
613        self.policy.dyn_eq(other.policy.as_ref())
614    }
615}
616
617impl Prop for RenderPolicyHandle {
618    fn prop_eq(&self, other: &Self) -> bool {
619        self == other
620    }
621}
622
623impl<S> From<S> for RenderPolicyHandle
624where
625    S: RenderPolicy,
626{
627    fn from(policy: S) -> Self {
628        Self {
629            policy: Box::new(policy),
630        }
631    }
632}
633
634/// # layout_primitive
635///
636/// Attach a layout policy, render policy, modifier chain, and optional child
637/// slot to the current component node.
638///
639/// ## Usage
640///
641/// Build framework or internal components that need to define custom node
642/// layout and rendering behavior.
643///
644/// ## Parameters
645///
646/// - `layout_policy` - pure layout policy used for measuring and placing the
647///   current node
648/// - `render_policy` - render policy used to record draw and compute commands
649///   for the current node
650/// - `modifier` - node-local modifier chain attached before child content is
651///   emitted
652/// - `child` - optional child slot rendered as this node's content subtree
653///
654/// ## Examples
655/// ```
656/// use tessera_ui::{
657///     Modifier, NoopRenderPolicy, RenderSlot,
658///     layout::{DefaultLayoutPolicy, layout_primitive},
659/// };
660///
661/// #[tessera_ui::tessera]
662/// fn primitive_example(modifier: Modifier, child: RenderSlot) {
663///     layout_primitive()
664///         .layout_policy(DefaultLayoutPolicy)
665///         .render_policy(NoopRenderPolicy)
666///         .modifier(modifier)
667///         .child(move || child.render());
668/// }
669/// ```
670#[tessera(crate)]
671pub fn layout_primitive(
672    #[prop(into)] layout_policy: LayoutPolicyHandle,
673    #[prop(into)] render_policy: RenderPolicyHandle,
674    modifier: Modifier,
675    child: Option<RenderSlot>,
676) {
677    modifier.attach();
678    TesseraRuntime::with_mut(|runtime| {
679        runtime.set_current_layout_policy_boxed(layout_policy.into_box());
680        runtime.set_current_render_policy_boxed(render_policy.into_box());
681    });
682    if let Some(child) = child {
683        child.render();
684    }
685}
686
687/// Default layout policy that stacks children at (0,0) and uses the bounding
688/// size.
689#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
690pub struct DefaultLayoutPolicy;
691
692impl LayoutPolicy for DefaultLayoutPolicy {
693    fn measure(
694        &self,
695        input: &LayoutInput<'_>,
696        output: &mut LayoutOutput<'_>,
697    ) -> Result<ComputedData, MeasurementError> {
698        if input.children_ids().is_empty() {
699            return Ok(ComputedData::min_from_constraint(
700                input.parent_constraint().as_ref(),
701            ));
702        }
703
704        let nodes_to_measure = input
705            .children_ids()
706            .iter()
707            .map(|&child_id| (child_id, *input.parent_constraint().as_ref()))
708            .collect();
709        let sizes = input.measure_children(nodes_to_measure)?;
710
711        let mut final_width = Px(0);
712        let mut final_height = Px(0);
713        for (child_id, size) in sizes {
714            output.place_child(child_id, PxPosition::ZERO);
715            final_width = final_width.max(size.width);
716            final_height = final_height.max(size.height);
717        }
718
719        Ok(ComputedData {
720            width: final_width,
721            height: final_height,
722        })
723    }
724}
725
726/// Default render policy that emits no draw commands.
727#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
728pub struct NoopRenderPolicy;
729
730impl RenderPolicy for NoopRenderPolicy {}