tessera_ui_basic_components/
boxed.rs1use derive_builder::Builder;
7use tessera_ui::{ComputedData, Constraint, DimensionValue, Px, PxPosition, tessera};
8
9use crate::alignment::Alignment;
10
11#[derive(Clone, Debug, Builder)]
13#[builder(pattern = "owned")]
14pub struct BoxedArgs {
15 #[builder(default)]
17 pub alignment: Alignment,
18 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
20 pub width: DimensionValue,
21 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
23 pub height: DimensionValue,
24}
25
26impl Default for BoxedArgs {
27 fn default() -> Self {
28 BoxedArgsBuilder::default()
29 .build()
30 .expect("BoxedArgsBuilder default build should succeed")
31 }
32}
33
34pub struct BoxedScope<'a> {
36 child_closures: &'a mut Vec<Box<dyn FnOnce() + Send + Sync>>,
37 child_alignments: &'a mut Vec<Option<Alignment>>,
38}
39
40impl<'a> BoxedScope<'a> {
41 pub fn child<F>(&mut self, child_closure: F)
43 where
44 F: FnOnce() + Send + Sync + 'static,
45 {
46 self.child_closures.push(Box::new(child_closure));
47 self.child_alignments.push(None);
48 }
49
50 pub fn child_with_alignment<F>(&mut self, alignment: Alignment, child_closure: F)
52 where
53 F: FnOnce() + Send + Sync + 'static,
54 {
55 self.child_closures.push(Box::new(child_closure));
56 self.child_alignments.push(Some(alignment));
57 }
58}
59
60fn resolve_final_dimension(dv: DimensionValue, largest_child: Px) -> Px {
63 match dv {
64 DimensionValue::Fixed(v) => v,
65 DimensionValue::Fill { min, max } => {
66 let mut v = max.unwrap_or(largest_child);
67 if let Some(min_v) = min {
68 v = v.max(min_v);
69 }
70 v
71 }
72 DimensionValue::Wrap { min, max } => {
73 let mut v = largest_child;
74 if let Some(min_v) = min {
75 v = v.max(min_v);
76 }
77 if let Some(max_v) = max {
78 v = v.min(max_v);
79 }
80 v
81 }
82 }
83}
84
85fn center_axis(container: Px, child: Px) -> Px {
87 (container - child) / 2
88}
89
90fn compute_child_offset(
92 alignment: Alignment,
93 container_w: Px,
94 container_h: Px,
95 child_w: Px,
96 child_h: Px,
97) -> (Px, Px) {
98 match alignment {
99 Alignment::TopStart => (Px(0), Px(0)),
100 Alignment::TopCenter => (center_axis(container_w, child_w), Px(0)),
101 Alignment::TopEnd => (container_w - child_w, Px(0)),
102 Alignment::CenterStart => (Px(0), center_axis(container_h, child_h)),
103 Alignment::Center => (
104 center_axis(container_w, child_w),
105 center_axis(container_h, child_h),
106 ),
107 Alignment::CenterEnd => (container_w - child_w, center_axis(container_h, child_h)),
108 Alignment::BottomStart => (Px(0), container_h - child_h),
109 Alignment::BottomCenter => (center_axis(container_w, child_w), container_h - child_h),
110 Alignment::BottomEnd => (container_w - child_w, container_h - child_h),
111 }
112}
113
114#[tessera]
146pub fn boxed<F>(args: BoxedArgs, scope_config: F)
147where
148 F: FnOnce(&mut BoxedScope),
149{
150 let mut child_closures: Vec<Box<dyn FnOnce() + Send + Sync>> = Vec::new();
151 let mut child_alignments: Vec<Option<Alignment>> = Vec::new();
152
153 {
154 let mut scope = BoxedScope {
155 child_closures: &mut child_closures,
156 child_alignments: &mut child_alignments,
157 };
158 scope_config(&mut scope);
159 }
160
161 let n = child_closures.len();
162 let child_alignments = child_alignments;
163
164 measure(Box::new(move |input| {
166 debug_assert_eq!(
167 input.children_ids.len(),
168 n,
169 "Mismatch between children defined in scope and runtime children count"
170 );
171
172 let boxed_intrinsic_constraint = Constraint::new(args.width, args.height);
173 let effective_constraint = boxed_intrinsic_constraint.merge(input.parent_constraint);
174
175 let mut max_child_width = Px(0);
177 let mut max_child_height = Px(0);
178 let mut children_sizes = vec![None; n];
179
180 let children_to_measure: Vec<_> = input
181 .children_ids
182 .iter()
183 .map(|&child_id| (child_id, effective_constraint))
184 .collect();
185
186 let children_results = input.measure_children(children_to_measure)?;
187
188 for (i, &child_id) in input.children_ids.iter().enumerate().take(n) {
189 if let Some(child_result) = children_results.get(&child_id) {
190 max_child_width = max_child_width.max(child_result.width);
191 max_child_height = max_child_height.max(child_result.height);
192 children_sizes[i] = Some(*child_result);
193 }
194 }
195
196 let final_width = resolve_final_dimension(effective_constraint.width, max_child_width);
198 let final_height = resolve_final_dimension(effective_constraint.height, max_child_height);
199
200 for (i, child_size_opt) in children_sizes.iter().enumerate() {
202 if let Some(child_size) = child_size_opt {
203 let child_id = input.children_ids[i];
204 let child_alignment = child_alignments[i].unwrap_or(args.alignment);
205 let (x, y) = compute_child_offset(
206 child_alignment,
207 final_width,
208 final_height,
209 child_size.width,
210 child_size.height,
211 );
212 input.place_child(child_id, PxPosition::new(x, y));
213 }
214 }
215
216 Ok(ComputedData {
217 width: final_width,
218 height: final_height,
219 })
220 }));
221
222 for child_closure in child_closures {
224 child_closure();
225 }
226}