tessera_ui_basic_components/
boxed.rs

1use derive_builder::Builder;
2use tessera_ui::{ComputedData, Constraint, DimensionValue, Px, PxPosition, place_node};
3use tessera_ui_macros::tessera;
4
5use crate::alignment::Alignment;
6
7pub use crate::boxed_ui;
8
9/// Arguments for the `Boxed` component.
10#[derive(Clone, Debug, Builder)]
11#[builder(pattern = "owned")]
12pub struct BoxedArgs {
13    /// The alignment of children within the `Boxed` container.
14    #[builder(default)]
15    pub alignment: Alignment,
16    /// Width behavior for the boxed container.
17    #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
18    pub width: DimensionValue,
19    /// Height behavior for the boxed container.
20    #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
21    pub height: DimensionValue,
22}
23
24impl Default for BoxedArgs {
25    fn default() -> Self {
26        BoxedArgsBuilder::default().build().unwrap()
27    }
28}
29
30/// `BoxedItem` represents a stackable child component.
31pub struct BoxedItem {
32    pub child: Box<dyn FnOnce() + Send + Sync>,
33}
34
35impl BoxedItem {
36    pub fn new(child: Box<dyn FnOnce() + Send + Sync>) -> Self {
37        BoxedItem { child }
38    }
39}
40
41/// A trait for converting various types into a `BoxedItem`.
42pub trait AsBoxedItem {
43    fn into_boxed_item(self) -> BoxedItem;
44}
45
46impl AsBoxedItem for BoxedItem {
47    fn into_boxed_item(self) -> BoxedItem {
48        self
49    }
50}
51
52impl<F: FnOnce() + Send + Sync + 'static> AsBoxedItem for F {
53    fn into_boxed_item(self) -> BoxedItem {
54        BoxedItem {
55            child: Box::new(self),
56        }
57    }
58}
59
60/// The `Boxed` component: stacks all its children, with the size being
61/// that of the largest child.
62#[tessera]
63pub fn boxed<const N: usize>(args: BoxedArgs, children_items_input: [impl AsBoxedItem; N]) {
64    let children_items: [BoxedItem; N] =
65        children_items_input.map(|item_input| item_input.into_boxed_item());
66
67    let mut child_closures = Vec::with_capacity(N);
68
69    for child_item in children_items {
70        child_closures.push(child_item.child);
71    }
72
73    measure(Box::new(move |input| {
74        let boxed_intrinsic_constraint = Constraint::new(args.width, args.height);
75        let effective_constraint = boxed_intrinsic_constraint.merge(input.parent_constraint);
76
77        let mut max_child_width = Px(0);
78        let mut max_child_height = Px(0);
79        let mut children_sizes = vec![None; N];
80
81        for i in 0..N {
82            let child_id = input.children_ids[i];
83            let child_result = tessera_ui::measure_node(
84                child_id,
85                &effective_constraint,
86                input.tree,
87                input.metadatas,
88                input.compute_resource_manager.clone(),
89                input.gpu,
90            )?;
91            max_child_width = max_child_width.max(child_result.width);
92            max_child_height = max_child_height.max(child_result.height);
93            children_sizes[i] = Some(child_result);
94        }
95
96        let final_width = match effective_constraint.width {
97            DimensionValue::Fixed(w) => w,
98            DimensionValue::Fill { min, max } => {
99                let mut w = max.unwrap_or(max_child_width);
100                if let Some(min_w) = min {
101                    w = w.max(min_w);
102                }
103                w
104            }
105            DimensionValue::Wrap { min, max } => {
106                let mut w = max_child_width;
107                if let Some(min_w) = min {
108                    w = w.max(min_w);
109                }
110                if let Some(max_w) = max {
111                    w = w.min(max_w);
112                }
113                w
114            }
115        };
116
117        let final_height = match effective_constraint.height {
118            DimensionValue::Fixed(h) => h,
119            DimensionValue::Fill { min, max } => {
120                let mut h = max.unwrap_or(max_child_height);
121                if let Some(min_h) = min {
122                    h = h.max(min_h);
123                }
124                h
125            }
126            DimensionValue::Wrap { min, max } => {
127                let mut h = max_child_height;
128                if let Some(min_h) = min {
129                    h = h.max(min_h);
130                }
131                if let Some(max_h) = max {
132                    h = h.min(max_h);
133                }
134                h
135            }
136        };
137
138        for (i, child_size_opt) in children_sizes.iter().enumerate() {
139            if let Some(child_size) = child_size_opt {
140                let child_id = input.children_ids[i];
141
142                let (x, y) = match args.alignment {
143                    Alignment::TopStart => (Px(0), Px(0)),
144                    Alignment::TopCenter => ((final_width - child_size.width) / 2, Px(0)),
145                    Alignment::TopEnd => (final_width - child_size.width, Px(0)),
146                    Alignment::CenterStart => (Px(0), (final_height - child_size.height) / 2),
147                    Alignment::Center => (
148                        (final_width - child_size.width) / 2,
149                        (final_height - child_size.height) / 2,
150                    ),
151                    Alignment::CenterEnd => (
152                        final_width - child_size.width,
153                        (final_height - child_size.height) / 2,
154                    ),
155                    Alignment::BottomStart => (Px(0), final_height - child_size.height),
156                    Alignment::BottomCenter => (
157                        (final_width - child_size.width) / 2,
158                        final_height - child_size.height,
159                    ),
160                    Alignment::BottomEnd => (
161                        final_width - child_size.width,
162                        final_height - child_size.height,
163                    ),
164                };
165                place_node(child_id, PxPosition::new(x, y), input.metadatas);
166            }
167        }
168
169        Ok(ComputedData {
170            width: final_width,
171            height: final_height,
172        })
173    }));
174
175    for child_closure in child_closures {
176        child_closure();
177    }
178}
179
180/// A macro for simplifying `Boxed` component declarations.
181#[macro_export]
182macro_rules! boxed_ui {
183    ($args:expr $(, $child:expr)* $(,)?) => {
184        {
185            use $crate::boxed::AsBoxedItem;
186            $crate::boxed::boxed($args, [
187                $(
188                    $child.into_boxed_item()
189                ),*
190            ])
191        }
192    };
193}