tessera_ui_basic_components/
boxed.rs1use derive_builder::Builder;
11use tessera_ui::{ComputedData, Constraint, DimensionValue, Px, PxPosition, place_node, tessera};
12
13use crate::alignment::Alignment;
14
15#[derive(Clone, Debug, Builder)]
17#[builder(pattern = "owned")]
18pub struct BoxedArgs {
19 #[builder(default)]
21 pub alignment: Alignment,
22 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
24 pub width: DimensionValue,
25 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
27 pub height: DimensionValue,
28}
29
30impl Default for BoxedArgs {
31 fn default() -> Self {
32 BoxedArgsBuilder::default().build().unwrap()
33 }
34}
35
36pub struct BoxedScope<'a> {
38 child_closures: &'a mut Vec<Box<dyn FnOnce() + Send + Sync>>,
39 child_alignments: &'a mut Vec<Option<Alignment>>,
40}
41
42impl<'a> BoxedScope<'a> {
43 pub fn child<F>(&mut self, child_closure: F)
45 where
46 F: FnOnce() + Send + Sync + 'static,
47 {
48 self.child_closures.push(Box::new(child_closure));
49 self.child_alignments.push(None);
50 }
51
52 pub fn child_with_alignment<F>(&mut self, alignment: Alignment, child_closure: F)
54 where
55 F: FnOnce() + Send + Sync + 'static,
56 {
57 self.child_closures.push(Box::new(child_closure));
58 self.child_alignments.push(Some(alignment));
59 }
60}
61
62fn resolve_final_dimension(dv: DimensionValue, largest_child: Px) -> Px {
65 match dv {
66 DimensionValue::Fixed(v) => v,
67 DimensionValue::Fill { min, max } => {
68 let mut v = max.unwrap_or(largest_child);
69 if let Some(min_v) = min {
70 v = v.max(min_v);
71 }
72 v
73 }
74 DimensionValue::Wrap { min, max } => {
75 let mut v = largest_child;
76 if let Some(min_v) = min {
77 v = v.max(min_v);
78 }
79 if let Some(max_v) = max {
80 v = v.min(max_v);
81 }
82 v
83 }
84 }
85}
86
87fn center_axis(container: Px, child: Px) -> Px {
89 (container - child) / 2
90}
91
92fn compute_child_offset(
94 alignment: Alignment,
95 container_w: Px,
96 container_h: Px,
97 child_w: Px,
98 child_h: Px,
99) -> (Px, Px) {
100 match alignment {
101 Alignment::TopStart => (Px(0), Px(0)),
102 Alignment::TopCenter => (center_axis(container_w, child_w), Px(0)),
103 Alignment::TopEnd => (container_w - child_w, Px(0)),
104 Alignment::CenterStart => (Px(0), center_axis(container_h, child_h)),
105 Alignment::Center => (
106 center_axis(container_w, child_w),
107 center_axis(container_h, child_h),
108 ),
109 Alignment::CenterEnd => (container_w - child_w, center_axis(container_h, child_h)),
110 Alignment::BottomStart => (Px(0), container_h - child_h),
111 Alignment::BottomCenter => (center_axis(container_w, child_w), container_h - child_h),
112 Alignment::BottomEnd => (container_w - child_w, container_h - child_h),
113 }
114}
115
116#[tessera]
125pub fn boxed<F>(args: BoxedArgs, scope_config: F)
126where
127 F: FnOnce(&mut BoxedScope),
128{
129 let mut child_closures: Vec<Box<dyn FnOnce() + Send + Sync>> = Vec::new();
130 let mut child_alignments: Vec<Option<Alignment>> = Vec::new();
131
132 {
133 let mut scope = BoxedScope {
134 child_closures: &mut child_closures,
135 child_alignments: &mut child_alignments,
136 };
137 scope_config(&mut scope);
138 }
139
140 let n = child_closures.len();
141 let child_alignments = child_alignments;
142
143 measure(Box::new(move |input| {
145 debug_assert_eq!(
146 input.children_ids.len(),
147 n,
148 "Mismatch between children defined in scope and runtime children count"
149 );
150
151 let boxed_intrinsic_constraint = Constraint::new(args.width, args.height);
152 let effective_constraint = boxed_intrinsic_constraint.merge(input.parent_constraint);
153
154 let mut max_child_width = Px(0);
156 let mut max_child_height = Px(0);
157 let mut children_sizes = vec![None; n];
158
159 let children_to_measure: Vec<_> = input
160 .children_ids
161 .iter()
162 .map(|&child_id| (child_id, effective_constraint))
163 .collect();
164
165 let children_results = input.measure_children(children_to_measure)?;
166
167 for (i, &child_id) in input.children_ids.iter().enumerate().take(n) {
168 if let Some(child_result) = children_results.get(&child_id) {
169 max_child_width = max_child_width.max(child_result.width);
170 max_child_height = max_child_height.max(child_result.height);
171 children_sizes[i] = Some(*child_result);
172 }
173 }
174
175 let final_width = resolve_final_dimension(effective_constraint.width, max_child_width);
177 let final_height = resolve_final_dimension(effective_constraint.height, max_child_height);
178
179 for (i, child_size_opt) in children_sizes.iter().enumerate() {
181 if let Some(child_size) = child_size_opt {
182 let child_id = input.children_ids[i];
183 let child_alignment = child_alignments[i].unwrap_or(args.alignment);
184 let (x, y) = compute_child_offset(
185 child_alignment,
186 final_width,
187 final_height,
188 child_size.width,
189 child_size.height,
190 );
191 place_node(child_id, PxPosition::new(x, y), input.metadatas);
192 }
193 }
194
195 Ok(ComputedData {
196 width: final_width,
197 height: final_height,
198 })
199 }));
200
201 for child_closure in child_closures {
203 child_closure();
204 }
205}