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