tessera_ui_basic_components/
boxed.rs1use derive_builder::Builder;
13use tessera_ui::{ComputedData, Constraint, DimensionValue, Px, PxPosition};
14use tessera_ui_macros::tessera;
15
16use crate::alignment::Alignment;
17
18pub use crate::boxed_ui;
19
20#[derive(Clone, Debug, Builder)]
22#[builder(pattern = "owned")]
23pub struct BoxedArgs {
24 #[builder(default)]
26 pub alignment: Alignment,
27 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
29 pub width: DimensionValue,
30 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
32 pub height: DimensionValue,
33}
34
35impl Default for BoxedArgs {
36 fn default() -> Self {
37 BoxedArgsBuilder::default().build().unwrap()
38 }
39}
40
41pub struct BoxedItem {
43 pub child: Box<dyn FnOnce() + Send + Sync>,
44}
45
46impl BoxedItem {
47 pub fn new(child: Box<dyn FnOnce() + Send + Sync>) -> Self {
48 BoxedItem { child }
49 }
50}
51
52pub trait AsBoxedItem {
54 fn into_boxed_item(self) -> BoxedItem;
55}
56
57impl AsBoxedItem for BoxedItem {
58 fn into_boxed_item(self) -> BoxedItem {
59 self
60 }
61}
62
63impl<F: FnOnce() + Send + Sync + 'static> AsBoxedItem for F {
64 fn into_boxed_item(self) -> BoxedItem {
65 BoxedItem {
66 child: Box::new(self),
67 }
68 }
69}
70
71#[tessera(render_fn=boxed_ui)]
102pub fn boxed<const N: usize>(args: BoxedArgs, children_items_input: [impl AsBoxedItem; N]) {
103 let children_items: [BoxedItem; N] =
104 children_items_input.map(|item_input| item_input.into_boxed_item());
105
106 let mut child_closures = Vec::with_capacity(N);
107
108 for child_item in children_items {
109 child_closures.push(child_item.child);
110 }
111
112 measure(Box::new(move |input| {
113 let boxed_intrinsic_constraint = Constraint::new(args.width, args.height);
114 let effective_constraint = boxed_intrinsic_constraint.merge(input.parent_constraint);
115
116 let mut max_child_width = Px(0);
117 let mut max_child_height = Px(0);
118 let mut children_sizes = vec![None; N];
119
120 for i in 0..N {
121 let Some(child_id) = input.children_ids.get(i).copied() else {
122 continue; };
124 let child_result = input.measure_child(child_id, &effective_constraint)?;
125 max_child_width = max_child_width.max(child_result.width);
126 max_child_height = max_child_height.max(child_result.height);
127 children_sizes[i] = Some(child_result);
128 }
129
130 let final_width = match effective_constraint.width {
131 DimensionValue::Fixed(w) => w,
132 DimensionValue::Fill { min, max } => {
133 let mut w = max.unwrap_or(max_child_width);
134 if let Some(min_w) = min {
135 w = w.max(min_w);
136 }
137 w
138 }
139 DimensionValue::Wrap { min, max } => {
140 let mut w = max_child_width;
141 if let Some(min_w) = min {
142 w = w.max(min_w);
143 }
144 if let Some(max_w) = max {
145 w = w.min(max_w);
146 }
147 w
148 }
149 };
150
151 let final_height = match effective_constraint.height {
152 DimensionValue::Fixed(h) => h,
153 DimensionValue::Fill { min, max } => {
154 let mut h = max.unwrap_or(max_child_height);
155 if let Some(min_h) = min {
156 h = h.max(min_h);
157 }
158 h
159 }
160 DimensionValue::Wrap { min, max } => {
161 let mut h = max_child_height;
162 if let Some(min_h) = min {
163 h = h.max(min_h);
164 }
165 if let Some(max_h) = max {
166 h = h.min(max_h);
167 }
168 h
169 }
170 };
171
172 for (i, child_size_opt) in children_sizes.iter().enumerate() {
173 if let Some(child_size) = child_size_opt {
174 let child_id = input.children_ids[i];
175
176 let (x, y) = match args.alignment {
177 Alignment::TopStart => (Px(0), Px(0)),
178 Alignment::TopCenter => ((final_width - child_size.width) / 2, Px(0)),
179 Alignment::TopEnd => (final_width - child_size.width, Px(0)),
180 Alignment::CenterStart => (Px(0), (final_height - child_size.height) / 2),
181 Alignment::Center => (
182 (final_width - child_size.width) / 2,
183 (final_height - child_size.height) / 2,
184 ),
185 Alignment::CenterEnd => (
186 final_width - child_size.width,
187 (final_height - child_size.height) / 2,
188 ),
189 Alignment::BottomStart => (Px(0), final_height - child_size.height),
190 Alignment::BottomCenter => (
191 (final_width - child_size.width) / 2,
192 final_height - child_size.height,
193 ),
194 Alignment::BottomEnd => (
195 final_width - child_size.width,
196 final_height - child_size.height,
197 ),
198 };
199 input.place_child(child_id, PxPosition::new(x, y));
200 }
201 }
202
203 Ok(ComputedData {
204 width: final_width,
205 height: final_height,
206 })
207 }));
208
209 for child_closure in child_closures {
210 child_closure();
211 }
212}
213
214#[macro_export]
216macro_rules! boxed_ui {
217 ($args:expr $(, $child:expr)* $(,)?) => {
218 {
219 use $crate::boxed::AsBoxedItem;
220 $crate::boxed::boxed($args, [
221 $(
222 $child.into_boxed_item()
223 ),*
224 ])
225 }
226 };
227}