Skip to main content

tessera_ui/
layout.rs

1//! Layout policies and measurement.
2
3use std::{
4    any::{Any, TypeId},
5    cell::RefCell,
6    collections::HashMap,
7    hash::{DefaultHasher, Hash, Hasher},
8};
9
10use crate::{
11    ComputeResourceManager, ComputedData, Constraint, MeasurementError, ParentConstraint, Px,
12    RenderSlot,
13    component_tree::{
14        ComponentNodeMetaData, ComponentNodeMetaDatas, ComponentNodeTree, LayoutContext,
15        measure_node,
16    },
17    modifier::{Modifier, OrderedModifierAction, ParentDataMap},
18    prop::Prop,
19    px::PxPosition,
20    render_graph::RenderFragment,
21    runtime::TesseraRuntime,
22    tessera,
23};
24
25#[derive(Clone, Copy)]
26pub(crate) struct ChildMeasure {
27    pub constraint: Constraint,
28    pub size: ComputedData,
29    pub consistent: bool,
30}
31
32/// Input for a pure layout policy.
33pub struct LayoutInput<'a> {
34    tree: &'a ComponentNodeTree,
35    parent_constraint: ParentConstraint<'a>,
36    children_ids: &'a [crate::NodeId],
37    metadatas: *mut ComponentNodeMetaDatas,
38    layout_ctx: Option<&'a LayoutContext<'a>>,
39    measured_children: RefCell<HashMap<crate::NodeId, ChildMeasure>>,
40}
41
42/// A direct child layout node available during a measure pass.
43#[derive(Clone, Copy)]
44pub struct LayoutChild<'a> {
45    node_id: crate::NodeId,
46    instance_key: u64,
47    tree: &'a ComponentNodeTree,
48    metadatas: *mut ComponentNodeMetaDatas,
49    layout_ctx: Option<&'a LayoutContext<'a>>,
50    measured_children: &'a RefCell<HashMap<crate::NodeId, ChildMeasure>>,
51}
52
53impl PartialEq for LayoutChild<'_> {
54    fn eq(&self, other: &Self) -> bool {
55        self.node_id == other.node_id
56    }
57}
58
59impl Eq for LayoutChild<'_> {}
60
61impl Hash for LayoutChild<'_> {
62    fn hash<H: Hasher>(&self, state: &mut H) {
63        self.node_id.hash(state);
64    }
65}
66
67impl<'a> LayoutChild<'a> {
68    /// Measures this child under the given constraint.
69    pub fn measure(&self, constraint: &Constraint) -> Result<MeasuredChild, MeasurementError> {
70        // SAFETY: Layout measurement is single-threaded. `LayoutChild` handles
71        // are created from a unique metadata borrow that outlives the measure
72        // pass and are only used during that pass.
73        let metadatas = unsafe { &mut *self.metadatas };
74        let size = measure_node(
75            self.node_id,
76            constraint,
77            self.tree,
78            metadatas,
79            self.layout_ctx,
80        )?;
81        let mut measured_children = self.measured_children.borrow_mut();
82        if let Some(entry) = measured_children.get_mut(&self.node_id) {
83            let consistent =
84                entry.consistent && entry.constraint == *constraint && entry.size == size;
85            entry.constraint = *constraint;
86            entry.size = size;
87            entry.consistent = consistent;
88        } else {
89            measured_children.insert(
90                self.node_id,
91                ChildMeasure {
92                    constraint: *constraint,
93                    size,
94                    consistent: true,
95                },
96            );
97        }
98        Ok(MeasuredChild {
99            instance_key: self.instance_key,
100            width: size.width,
101            height: size.height,
102        })
103    }
104
105    /// Measures this child without recording it for layout cache keys.
106    pub fn measure_untracked(
107        &self,
108        constraint: &Constraint,
109    ) -> Result<MeasuredChild, MeasurementError> {
110        // SAFETY: See `LayoutChild::measure`.
111        let metadatas = unsafe { &mut *self.metadatas };
112        let size = measure_node(
113            self.node_id,
114            constraint,
115            self.tree,
116            metadatas,
117            self.layout_ctx,
118        )?;
119        Ok(MeasuredChild {
120            instance_key: self.instance_key,
121            width: size.width,
122            height: size.height,
123        })
124    }
125
126    /// Reads a typed parent-data payload from this direct child layout node.
127    pub fn parent_data<T>(&self) -> Option<T>
128    where
129        T: Clone + Send + Sync + 'static,
130    {
131        let node = self.tree.get(self.node_id)?;
132        let mut data: ParentDataMap = HashMap::default();
133        for action in node.get().modifier.ordered_actions() {
134            if let OrderedModifierAction::ParentData(node) = action {
135                node.apply_parent_data(&mut data);
136            }
137        }
138        let value = data.get(&TypeId::of::<T>())?;
139        value.downcast_ref::<T>().cloned()
140    }
141
142    pub(crate) const fn instance_key(&self) -> u64 {
143        self.instance_key
144    }
145}
146
147/// A measured child returned from [`LayoutChild::measure`].
148#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
149pub struct MeasuredChild {
150    instance_key: u64,
151    /// The measured width of the child.
152    pub width: Px,
153    /// The measured height of the child.
154    pub height: Px,
155}
156
157impl MeasuredChild {
158    /// Returns the measured size for this child.
159    pub const fn size(&self) -> ComputedData {
160        ComputedData {
161            width: self.width,
162            height: self.height,
163        }
164    }
165
166    pub(crate) const fn instance_key(&self) -> u64 {
167        self.instance_key
168    }
169}
170
171/// A direct child available during a placement-only pass.
172#[derive(Clone, Copy)]
173pub struct PlacementChild<'a> {
174    node_id: crate::NodeId,
175    instance_key: u64,
176    tree: &'a ComponentNodeTree,
177    size: ComputedData,
178}
179
180impl<'a> PlacementChild<'a> {
181    /// Returns the cached measured size from the preceding measure pass.
182    pub const fn size(&self) -> ComputedData {
183        self.size
184    }
185
186    /// Reads a typed parent-data payload from the child.
187    pub fn parent_data<T>(&self) -> Option<T>
188    where
189        T: Clone + Send + Sync + 'static,
190    {
191        let node = self.tree.get(self.node_id)?;
192        let mut data: ParentDataMap = HashMap::default();
193        for action in node.get().modifier.ordered_actions() {
194            if let OrderedModifierAction::ParentData(node) = action {
195                node.apply_parent_data(&mut data);
196            }
197        }
198        let value = data.get(&TypeId::of::<T>())?;
199        value.downcast_ref::<T>().cloned()
200    }
201
202    pub(crate) const fn instance_key(&self) -> u64 {
203        self.instance_key
204    }
205}
206
207/// Shared measurement helpers available during a layout pass.
208pub struct MeasureScope<'a> {
209    tree: &'a ComponentNodeTree,
210    metadatas: *mut ComponentNodeMetaDatas,
211    layout_ctx: Option<&'a LayoutContext<'a>>,
212    measured_children: &'a RefCell<HashMap<crate::NodeId, ChildMeasure>>,
213    parent_constraint: ParentConstraint<'a>,
214    children_ids: &'a [crate::NodeId],
215}
216
217impl<'a> MeasureScope<'a> {
218    pub(crate) fn new(
219        tree: &'a ComponentNodeTree,
220        metadatas: *mut ComponentNodeMetaDatas,
221        layout_ctx: Option<&'a LayoutContext<'a>>,
222        measured_children: &'a RefCell<HashMap<crate::NodeId, ChildMeasure>>,
223        parent_constraint: ParentConstraint<'a>,
224        children_ids: &'a [crate::NodeId],
225    ) -> Self {
226        Self {
227            tree,
228            metadatas,
229            layout_ctx,
230            measured_children,
231            parent_constraint,
232            children_ids,
233        }
234    }
235
236    /// Returns the inherited constraint.
237    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
238        self.parent_constraint
239    }
240
241    /// Returns the direct child layout nodes of the current node.
242    pub fn children(&self) -> Vec<LayoutChild<'_>> {
243        self.children_ids
244            .iter()
245            .map(|&node_id| {
246                let instance_key = self
247                    .tree
248                    .get(node_id)
249                    .expect("Direct child layout node must exist")
250                    .get()
251                    .instance_key;
252                LayoutChild {
253                    node_id,
254                    instance_key,
255                    tree: self.tree,
256                    metadatas: self.metadatas,
257                    layout_ctx: self.layout_ctx,
258                    measured_children: self.measured_children,
259                }
260            })
261            .collect()
262    }
263}
264
265/// Shared placement helpers available during a cached placement pass.
266pub struct PlacementScope<'a> {
267    tree: &'a ComponentNodeTree,
268    parent_constraint: ParentConstraint<'a>,
269    children_ids: &'a [crate::NodeId],
270    child_sizes: &'a HashMap<crate::NodeId, ComputedData>,
271    size: ComputedData,
272}
273
274impl<'a> PlacementScope<'a> {
275    pub(crate) fn new(
276        tree: &'a ComponentNodeTree,
277        parent_constraint: ParentConstraint<'a>,
278        children_ids: &'a [crate::NodeId],
279        child_sizes: &'a HashMap<crate::NodeId, ComputedData>,
280        size: ComputedData,
281    ) -> Self {
282        Self {
283            tree,
284            parent_constraint,
285            children_ids,
286            child_sizes,
287            size,
288        }
289    }
290
291    /// Returns the inherited constraint.
292    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
293        self.parent_constraint
294    }
295
296    /// Returns the direct child layout nodes of the current node.
297    pub fn children(&self) -> Vec<PlacementChild<'_>> {
298        self.children_ids
299            .iter()
300            .map(|&node_id| {
301                let node = self
302                    .tree
303                    .get(node_id)
304                    .expect("Direct child layout node must exist");
305                PlacementChild {
306                    node_id,
307                    instance_key: node.get().instance_key,
308                    tree: self.tree,
309                    size: self
310                        .child_sizes
311                        .get(&node_id)
312                        .copied()
313                        .expect("Placement child size must exist for a direct child layout node"),
314                }
315            })
316            .collect()
317    }
318
319    /// Returns the cached size of the current node.
320    pub const fn size(&self) -> ComputedData {
321        self.size
322    }
323}
324
325impl<'a> LayoutInput<'a> {
326    pub(crate) fn new(
327        tree: &'a ComponentNodeTree,
328        parent_constraint: ParentConstraint<'a>,
329        children_ids: &'a [crate::NodeId],
330        metadatas: *mut ComponentNodeMetaDatas,
331        layout_ctx: Option<&'a LayoutContext<'a>>,
332    ) -> Self {
333        Self {
334            tree,
335            parent_constraint,
336            children_ids,
337            metadatas,
338            layout_ctx,
339            measured_children: RefCell::new(HashMap::new()),
340        }
341    }
342
343    /// Returns the inherited constraint.
344    pub const fn parent_constraint(&self) -> ParentConstraint<'a> {
345        self.parent_constraint
346    }
347
348    pub(crate) fn take_measured_children(&self) -> HashMap<crate::NodeId, ChildMeasure> {
349        std::mem::take(&mut *self.measured_children.borrow_mut())
350    }
351
352    pub(crate) fn extend_measured_children(&self, children: HashMap<crate::NodeId, ChildMeasure>) {
353        let mut measured_children = self.measured_children.borrow_mut();
354        for (child_id, measurement) in children {
355            if let Some(entry) = measured_children.get_mut(&child_id) {
356                let consistent = entry.consistent
357                    && entry.constraint == measurement.constraint
358                    && entry.size == measurement.size
359                    && measurement.consistent;
360                entry.constraint = measurement.constraint;
361                entry.size = measurement.size;
362                entry.consistent = consistent;
363            } else {
364                measured_children.insert(child_id, measurement);
365            }
366        }
367    }
368
369    pub(crate) fn measure_scope(&self) -> MeasureScope<'_> {
370        MeasureScope::new(
371            self.tree,
372            self.metadatas,
373            self.layout_ctx,
374            &self.measured_children,
375            self.parent_constraint,
376            self.children_ids,
377        )
378    }
379
380    /// Returns the direct child layout nodes of the current node.
381    pub fn children(&self) -> Vec<LayoutChild<'_>> {
382        self.children_ids
383            .iter()
384            .map(|&node_id| {
385                let instance_key = self
386                    .tree
387                    .get(node_id)
388                    .expect("Direct child layout node must exist")
389                    .get()
390                    .instance_key;
391                LayoutChild {
392                    node_id,
393                    instance_key,
394                    tree: self.tree,
395                    metadatas: self.metadatas,
396                    layout_ctx: self.layout_ctx,
397                    measured_children: &self.measured_children,
398                }
399            })
400            .collect()
401    }
402}
403
404/// Cached output from pure layout.
405#[derive(Clone)]
406pub struct LayoutResult {
407    /// The computed size for the node.
408    pub size: ComputedData,
409    /// Child placements keyed by instance_key.
410    pub placements: Vec<(u64, PxPosition)>,
411}
412
413impl LayoutResult {
414    /// Creates a layout result for the current node.
415    pub const fn new(size: ComputedData) -> Self {
416        Self {
417            size,
418            placements: Vec::new(),
419        }
420    }
421
422    /// Places a child relative to the current node origin.
423    pub fn place_child<T>(&mut self, child: T, position: PxPosition)
424    where
425        T: LayoutPlacementTarget,
426    {
427        self.placements.push((child.instance_key(), position));
428    }
429
430    /// Replaces the computed size while preserving recorded placements.
431    pub const fn with_size(mut self, size: ComputedData) -> Self {
432        self.size = size;
433        self
434    }
435
436    /// Consumes the result and returns only the recorded placements.
437    pub fn into_placements(self) -> Vec<(u64, PxPosition)> {
438        self.placements
439    }
440}
441
442impl Default for LayoutResult {
443    fn default() -> Self {
444        Self::new(ComputedData::ZERO)
445    }
446}
447
448/// A direct child handle that can be placed by [`LayoutResult`].
449pub trait LayoutPlacementTarget: Copy {
450    #[doc(hidden)]
451    fn instance_key(&self) -> u64;
452}
453
454impl LayoutPlacementTarget for LayoutChild<'_> {
455    fn instance_key(&self) -> u64 {
456        self.instance_key()
457    }
458}
459
460impl LayoutPlacementTarget for MeasuredChild {
461    fn instance_key(&self) -> u64 {
462        self.instance_key()
463    }
464}
465
466impl LayoutPlacementTarget for PlacementChild<'_> {
467    fn instance_key(&self) -> u64 {
468        self.instance_key()
469    }
470}
471
472/// Input for a render record pass.
473pub struct RenderInput<'a> {
474    current_node_id: crate::NodeId,
475    metadatas: &'a mut ComponentNodeMetaDatas,
476    /// Mutable GPU compute resources for the current frame.
477    pub compute_resource_manager: &'a mut ComputeResourceManager,
478    /// GPU device for issuing render-side allocations.
479    pub gpu: &'a wgpu::Device,
480}
481
482impl<'a> RenderInput<'a> {
483    pub(crate) fn new(
484        current_node_id: crate::NodeId,
485        metadatas: &'a mut ComponentNodeMetaDatas,
486        compute_resource_manager: &'a mut ComputeResourceManager,
487        gpu: &'a wgpu::Device,
488    ) -> Self {
489        Self {
490            current_node_id,
491            metadatas,
492            compute_resource_manager,
493            gpu,
494        }
495    }
496
497    /// Returns a mutable render metadata handle for the current node.
498    pub fn metadata_mut(&mut self) -> RenderMetadataMut<'_> {
499        let metadata = self
500            .metadatas
501            .get_mut(&self.current_node_id)
502            .expect("Metadata for current node must exist during record");
503        RenderMetadataMut { metadata }
504    }
505}
506
507/// Mutable render metadata available during the record pass.
508pub struct RenderMetadataMut<'a> {
509    metadata: &'a mut ComponentNodeMetaData,
510}
511
512impl RenderMetadataMut<'_> {
513    /// Returns the computed size of the current node.
514    pub fn computed_data(&self) -> Option<ComputedData> {
515        self.metadata.computed_data
516    }
517
518    /// Returns the render fragment for the current node.
519    pub fn fragment_mut(&mut self) -> &mut RenderFragment {
520        self.metadata.fragment_mut()
521    }
522
523    /// Enables or disables child clipping for the current node.
524    pub fn set_clips_children(&mut self, clips_children: bool) {
525        self.metadata.clips_children = clips_children;
526    }
527
528    /// Multiplies the current node opacity by the provided factor.
529    pub fn multiply_opacity(&mut self, opacity: f32) {
530        self.metadata.opacity *= opacity;
531    }
532}
533
534/// Pure layout policy for measuring and placing child nodes.
535pub trait LayoutPolicy: Send + Sync + Clone + PartialEq + 'static {
536    /// Computes layout for the current node.
537    fn measure(&self, scope: &MeasureScope<'_>) -> Result<LayoutResult, MeasurementError>;
538
539    /// Compares measurement-relevant state for layout dirty tracking.
540    fn measure_eq(&self, other: &Self) -> bool {
541        self == other
542    }
543
544    /// Compares placement-relevant state for layout dirty tracking.
545    fn placement_eq(&self, other: &Self) -> bool {
546        self == other
547    }
548
549    /// Recomputes child placements using cached child measurements.
550    ///
551    /// Returns placements when the placement pass was handled without
552    /// remeasurement.
553    fn place_children(&self, _scope: &PlacementScope<'_>) -> Option<Vec<(u64, PxPosition)>> {
554        None
555    }
556}
557
558/// Render policy for recording draw and compute commands for the current node.
559pub trait RenderPolicy: Send + Sync + Clone + PartialEq + 'static {
560    /// Records draw and compute commands for the current node.
561    fn record(&self, _input: &mut RenderInput<'_>) {}
562}
563
564/// Type-erased layout policy used by the runtime.
565#[doc(hidden)]
566pub trait LayoutPolicyDyn: Send + Sync {
567    /// Returns a typed reference for downcasting.
568    fn as_any(&self) -> &dyn Any;
569    /// Measures layout using a type-erased policy.
570    fn measure_dyn(&self, scope: &MeasureScope<'_>) -> Result<LayoutResult, MeasurementError>;
571    /// Recomputes child placements using cached child measurements.
572    fn place_children_dyn(&self, scope: &PlacementScope<'_>) -> Option<Vec<(u64, PxPosition)>>;
573    /// Compares two type-erased policies for equality.
574    fn dyn_eq(&self, other: &dyn LayoutPolicyDyn) -> bool;
575    /// Compares two type-erased policies for measurement-relevant equality.
576    fn dyn_measure_eq(&self, other: &dyn LayoutPolicyDyn) -> bool;
577    /// Compares two type-erased policies for placement-relevant equality.
578    fn dyn_placement_eq(&self, other: &dyn LayoutPolicyDyn) -> bool;
579    /// Clones the type-erased policy.
580    fn clone_box(&self) -> Box<dyn LayoutPolicyDyn>;
581}
582
583impl<T> LayoutPolicyDyn for T
584where
585    T: LayoutPolicy,
586{
587    fn as_any(&self) -> &dyn Any {
588        self
589    }
590
591    fn measure_dyn(&self, scope: &MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
592        LayoutPolicy::measure(self, scope)
593    }
594
595    fn place_children_dyn(&self, scope: &PlacementScope<'_>) -> Option<Vec<(u64, PxPosition)>> {
596        LayoutPolicy::place_children(self, scope)
597    }
598
599    fn dyn_eq(&self, other: &dyn LayoutPolicyDyn) -> bool {
600        other
601            .as_any()
602            .downcast_ref::<T>()
603            .is_some_and(|other| self == other)
604    }
605
606    fn dyn_measure_eq(&self, other: &dyn LayoutPolicyDyn) -> bool {
607        other
608            .as_any()
609            .downcast_ref::<T>()
610            .is_some_and(|other| LayoutPolicy::measure_eq(self, other))
611    }
612
613    fn dyn_placement_eq(&self, other: &dyn LayoutPolicyDyn) -> bool {
614        other
615            .as_any()
616            .downcast_ref::<T>()
617            .is_some_and(|other| LayoutPolicy::placement_eq(self, other))
618    }
619
620    fn clone_box(&self) -> Box<dyn LayoutPolicyDyn> {
621        Box::new(self.clone())
622    }
623}
624
625/// Type-erased render policy used by the runtime.
626#[doc(hidden)]
627pub trait RenderPolicyDyn: Send + Sync {
628    /// Returns a typed reference for downcasting.
629    fn as_any(&self) -> &dyn Any;
630    /// Records render commands using a type-erased policy.
631    fn record_dyn(&self, input: &mut RenderInput<'_>);
632    /// Compares two type-erased policies for equality.
633    fn dyn_eq(&self, other: &dyn RenderPolicyDyn) -> bool;
634    /// Clones the type-erased policy.
635    fn clone_box(&self) -> Box<dyn RenderPolicyDyn>;
636}
637
638impl<T> RenderPolicyDyn for T
639where
640    T: RenderPolicy,
641{
642    fn as_any(&self) -> &dyn Any {
643        self
644    }
645
646    fn record_dyn(&self, input: &mut RenderInput<'_>) {
647        RenderPolicy::record(self, input);
648    }
649
650    fn dyn_eq(&self, other: &dyn RenderPolicyDyn) -> bool {
651        other
652            .as_any()
653            .downcast_ref::<T>()
654            .is_some_and(|other| self == other)
655    }
656
657    fn clone_box(&self) -> Box<dyn RenderPolicyDyn> {
658        Box::new(self.clone())
659    }
660}
661
662impl Clone for Box<dyn LayoutPolicyDyn> {
663    fn clone(&self) -> Self {
664        self.clone_box()
665    }
666}
667
668impl Clone for Box<dyn RenderPolicyDyn> {
669    fn clone(&self) -> Self {
670        self.clone_box()
671    }
672}
673
674/// Type-erased layout policy handle consumed by [`layout`].
675///
676/// Most callers do not construct this type directly. Instead, pass any
677/// [`LayoutPolicy`] to the `layout_policy(...)` builder method and rely on the
678/// `From<T>` conversion.
679#[derive(Clone)]
680pub struct LayoutPolicyHandle {
681    policy: Box<dyn LayoutPolicyDyn>,
682}
683
684impl LayoutPolicyHandle {
685    pub(crate) fn into_box(self) -> Box<dyn LayoutPolicyDyn> {
686        self.policy
687    }
688}
689
690impl Default for LayoutPolicyHandle {
691    fn default() -> Self {
692        Self {
693            policy: Box::new(DefaultLayoutPolicy),
694        }
695    }
696}
697
698impl PartialEq for LayoutPolicyHandle {
699    fn eq(&self, other: &Self) -> bool {
700        self.policy.dyn_eq(other.policy.as_ref())
701    }
702}
703
704impl Prop for LayoutPolicyHandle {
705    fn prop_eq(&self, other: &Self) -> bool {
706        self == other
707    }
708}
709
710impl<S> From<S> for LayoutPolicyHandle
711where
712    S: LayoutPolicy,
713{
714    fn from(policy: S) -> Self {
715        Self {
716            policy: Box::new(policy),
717        }
718    }
719}
720
721/// Type-erased render policy handle consumed by [`layout`].
722///
723/// Most callers do not construct this type directly. Instead, pass any
724/// [`RenderPolicy`] to the `render_policy(...)` builder method and rely on the
725/// `From<T>` conversion.
726#[derive(Clone)]
727pub struct RenderPolicyHandle {
728    policy: Box<dyn RenderPolicyDyn>,
729}
730
731impl RenderPolicyHandle {
732    pub(crate) fn into_box(self) -> Box<dyn RenderPolicyDyn> {
733        self.policy
734    }
735}
736
737impl Default for RenderPolicyHandle {
738    fn default() -> Self {
739        Self {
740            policy: Box::new(NoopRenderPolicy),
741        }
742    }
743}
744
745impl PartialEq for RenderPolicyHandle {
746    fn eq(&self, other: &Self) -> bool {
747        self.policy.dyn_eq(other.policy.as_ref())
748    }
749}
750
751impl Prop for RenderPolicyHandle {
752    fn prop_eq(&self, other: &Self) -> bool {
753        self == other
754    }
755}
756
757impl<S> From<S> for RenderPolicyHandle
758where
759    S: RenderPolicy,
760{
761    fn from(policy: S) -> Self {
762        Self {
763            policy: Box::new(policy),
764        }
765    }
766}
767
768/// # layout
769///
770/// Emit an explicit layout node with a layout policy, render policy, modifier
771/// chain, and optional child slot.
772///
773/// ## Usage
774///
775/// Build framework or internal components that need to define explicit layout
776/// and rendering behavior inside a tessera composition boundary.
777///
778/// ## Parameters
779///
780/// - `layout_policy` - pure layout policy used for measuring and placing the
781///   emitted layout node, defaulting to [`DefaultLayoutPolicy`]
782/// - `render_policy` - render policy used to record draw and compute commands
783///   for the emitted layout node, defaulting to [`NoopRenderPolicy`]
784/// - `modifier` - node-local modifier chain attached before child content is
785///   emitted, defaulting to an empty [`Modifier`]
786/// - `child` - optional child slot rendered as the emitted node's content
787///   subtree
788///
789/// ## Examples
790/// ```
791/// use tessera_ui::{
792///     Modifier, NoopRenderPolicy, RenderSlot,
793///     layout::{DefaultLayoutPolicy, layout},
794/// };
795///
796/// #[tessera_ui::tessera]
797/// fn primitive_example(modifier: Modifier, child: RenderSlot) {
798///     layout()
799///         .layout_policy(DefaultLayoutPolicy)
800///         .render_policy(NoopRenderPolicy)
801///         .modifier(modifier)
802///         .child(move || child.render());
803/// }
804/// ```
805#[tessera(crate)]
806pub fn layout(
807    #[prop(into)] layout_policy: Option<LayoutPolicyHandle>,
808    #[prop(into)] render_policy: Option<RenderPolicyHandle>,
809    modifier: Option<Modifier>,
810    child: Option<RenderSlot>,
811) {
812    let layout_policy = layout_policy.unwrap_or(LayoutPolicyHandle::from(DefaultLayoutPolicy));
813    let render_policy = render_policy.unwrap_or(RenderPolicyHandle::from(NoopRenderPolicy));
814    let modifier = modifier.unwrap_or_default();
815    let layout_node_component_type_id = layout_node_component_type_id();
816    let layout_node_id =
817        crate::__private::register_layout_node("layout", layout_node_component_type_id);
818    let _layout_node_ctx_guard = crate::__private::push_current_node(
819        layout_node_id,
820        layout_node_component_type_id,
821        "layout",
822    );
823    let layout_instance_key = crate::__private::current_instance_key();
824    let layout_instance_logic_id = crate::__private::current_instance_logic_id();
825    let _layout_scope_guard = {
826        struct LayoutNodeScopeGuard;
827
828        impl Drop for LayoutNodeScopeGuard {
829            fn drop(&mut self) {
830                crate::__private::finish_component_node();
831            }
832        }
833
834        LayoutNodeScopeGuard
835    };
836
837    crate::__private::set_current_node_identity(layout_instance_key, layout_instance_logic_id);
838    modifier.attach();
839    TesseraRuntime::with_mut(|runtime| {
840        runtime.set_current_layout_policy_boxed(layout_policy.into_box());
841        runtime.set_current_render_policy_boxed(render_policy.into_box());
842    });
843    if let Some(child) = child {
844        child.render();
845    }
846}
847
848fn layout_node_component_type_id() -> u64 {
849    let mut hasher = DefaultHasher::new();
850    "layout_node".hash(&mut hasher);
851    hasher.finish()
852}
853
854/// Default layout policy that stacks children at (0,0) and uses the bounding
855/// size.
856#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
857pub struct DefaultLayoutPolicy;
858
859impl LayoutPolicy for DefaultLayoutPolicy {
860    fn measure(&self, scope: &MeasureScope<'_>) -> Result<LayoutResult, MeasurementError> {
861        let children = scope.children();
862        if children.is_empty() {
863            return Ok(LayoutResult::new(ComputedData::min_from_constraint(
864                scope.parent_constraint().as_ref(),
865            )));
866        }
867
868        let mut result = LayoutResult::new(ComputedData::ZERO);
869        let mut final_width = Px(0);
870        let mut final_height = Px(0);
871        for child in children {
872            let measurement = child.measure(scope.parent_constraint().as_ref())?;
873            result.place_child(measurement, PxPosition::ZERO);
874            let size = measurement.size();
875            final_width = final_width.max(size.width);
876            final_height = final_height.max(size.height);
877        }
878
879        result.size = ComputedData {
880            width: final_width,
881            height: final_height,
882        };
883        Ok(result)
884    }
885}
886
887/// Default render policy that emits no draw commands.
888#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
889pub struct NoopRenderPolicy;
890
891impl RenderPolicy for NoopRenderPolicy {}