1use derive_builder::Builder;
23use tessera_ui::{
24 ComponentNodeMetaDatas, ComputedData, Constraint, DimensionValue, MeasureInput,
25 MeasurementError, NodeId, Px, PxPosition, place_node, tessera,
26};
27
28use crate::alignment::{CrossAxisAlignment, MainAxisAlignment};
29
30#[derive(Builder, Clone, Debug)]
32#[builder(pattern = "owned")]
33pub struct ColumnArgs {
34 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
36 pub width: DimensionValue,
37 #[builder(default = "DimensionValue::Wrap { min: None, max: None }")]
39 pub height: DimensionValue,
40 #[builder(default = "MainAxisAlignment::Start")]
42 pub main_axis_alignment: MainAxisAlignment,
43 #[builder(default = "CrossAxisAlignment::Start")]
45 pub cross_axis_alignment: CrossAxisAlignment,
46}
47
48impl Default for ColumnArgs {
49 fn default() -> Self {
50 ColumnArgsBuilder::default().build().unwrap()
51 }
52}
53
54pub struct ColumnScope<'a> {
56 child_closures: &'a mut Vec<Box<dyn FnOnce() + Send + Sync>>,
57 child_weights: &'a mut Vec<Option<f32>>,
58}
59
60impl<'a> ColumnScope<'a> {
61 pub fn child<F>(&mut self, child_closure: F)
63 where
64 F: FnOnce() + Send + Sync + 'static,
65 {
66 self.child_closures.push(Box::new(child_closure));
67 self.child_weights.push(None);
68 }
69
70 pub fn child_weighted<F>(&mut self, child_closure: F, weight: f32)
72 where
73 F: FnOnce() + Send + Sync + 'static,
74 {
75 self.child_closures.push(Box::new(child_closure));
76 self.child_weights.push(Some(weight));
77 }
78}
79
80#[tessera]
86pub fn column<F>(args: ColumnArgs, scope_config: F)
87where
88 F: FnOnce(&mut ColumnScope),
89{
90 let mut child_closures: Vec<Box<dyn FnOnce() + Send + Sync>> = Vec::new();
91 let mut child_weights: Vec<Option<f32>> = Vec::new();
92
93 {
94 let mut scope = ColumnScope {
95 child_closures: &mut child_closures,
96 child_weights: &mut child_weights,
97 };
98 scope_config(&mut scope);
99 }
100
101 let n = child_closures.len();
102
103 measure(Box::new(
104 move |input| -> Result<ComputedData, MeasurementError> {
105 assert_eq!(
106 input.children_ids.len(),
107 n,
108 "Mismatch between children defined in scope and runtime children count"
109 );
110
111 let column_intrinsic_constraint = Constraint::new(args.width, args.height);
112 let column_effective_constraint =
113 column_intrinsic_constraint.merge(input.parent_constraint);
114
115 let mut children_sizes = vec![None; n];
116 let mut max_child_width = Px(0);
117
118 let should_use_weight_for_height = matches!(
119 column_effective_constraint.height,
120 DimensionValue::Fixed(_)
121 | DimensionValue::Fill { max: Some(_), .. }
122 | DimensionValue::Wrap { max: Some(_), .. }
123 );
124
125 let (final_column_width, final_column_height, total_measured_children_height) =
126 if should_use_weight_for_height {
127 measure_weighted_column(
128 input,
129 &args,
130 &child_weights,
131 &column_effective_constraint,
132 &mut children_sizes,
133 &mut max_child_width,
134 )?
135 } else {
136 measure_unweighted_column(
137 input,
138 &args,
139 &column_effective_constraint,
140 &mut children_sizes,
141 &mut max_child_width,
142 )?
143 };
144
145 place_children_with_alignment(&PlaceChildrenArgs {
146 children_sizes: &children_sizes,
147 children_ids: input.children_ids,
148 metadatas: input.metadatas,
149 final_column_width,
150 final_column_height,
151 total_children_height: total_measured_children_height,
152 main_axis_alignment: args.main_axis_alignment,
153 cross_axis_alignment: args.cross_axis_alignment,
154 child_count: n,
155 });
156
157 Ok(ComputedData {
158 width: final_column_width,
159 height: final_column_height,
160 })
161 },
162 ));
163
164 for child_closure in child_closures {
165 child_closure();
166 }
167}
168
169struct PlaceChildrenArgs<'a> {
171 children_sizes: &'a [Option<ComputedData>],
172 children_ids: &'a [NodeId],
173 metadatas: &'a ComponentNodeMetaDatas,
174 final_column_width: Px,
175 final_column_height: Px,
176 total_children_height: Px,
177 main_axis_alignment: MainAxisAlignment,
178 cross_axis_alignment: CrossAxisAlignment,
179 child_count: usize,
180}
181
182fn classify_children(child_weights: &[Option<f32>]) -> (Vec<usize>, Vec<usize>, f32) {
184 let mut weighted_indices = Vec::new();
185 let mut unweighted_indices = Vec::new();
186 let mut total_weight = 0.0;
187 for (i, weight_opt) in child_weights.iter().enumerate() {
188 if let Some(w) = weight_opt {
189 if *w > 0.0 {
190 weighted_indices.push(i);
191 total_weight += w;
192 } else {
193 unweighted_indices.push(i);
194 }
195 } else {
196 unweighted_indices.push(i);
197 }
198 }
199 (weighted_indices, unweighted_indices, total_weight)
200}
201
202fn measure_unweighted_children_for_column(
205 input: &MeasureInput,
206 indices: &[usize],
207 children_sizes: &mut [Option<ComputedData>],
208 max_child_width: &mut Px,
209 column_effective_constraint: &Constraint,
210) -> Result<Px, MeasurementError> {
211 let mut total = Px(0);
212
213 let parent_offered_constraint_for_child = Constraint::new(
214 column_effective_constraint.width,
215 DimensionValue::Wrap {
216 min: None,
217 max: column_effective_constraint.height.get_max(),
218 },
219 );
220
221 let children_to_measure: Vec<_> = indices
222 .iter()
223 .map(|&child_idx| {
224 (
225 input.children_ids[child_idx],
226 parent_offered_constraint_for_child,
227 )
228 })
229 .collect();
230
231 let children_results = input.measure_children(children_to_measure)?;
232
233 for &child_idx in indices {
234 let child_id = input.children_ids[child_idx];
235 if let Some(child_result) = children_results.get(&child_id) {
236 children_sizes[child_idx] = Some(*child_result);
237 total += child_result.height;
238 *max_child_width = (*max_child_width).max(child_result.width);
239 }
240 }
241
242 Ok(total)
243}
244
245fn measure_weighted_children_for_column(
247 input: &MeasureInput,
248 weighted_indices: &[usize],
249 children_sizes: &mut [Option<ComputedData>],
250 max_child_width: &mut Px,
251 remaining_height: Px,
252 total_weight: f32,
253 column_effective_constraint: &Constraint,
254 child_weights: &[Option<f32>],
255) -> Result<(), MeasurementError> {
256 if total_weight <= 0.0 {
257 return Ok(());
258 }
259
260 let children_to_measure: Vec<_> = weighted_indices
261 .iter()
262 .map(|&child_idx| {
263 let child_weight = child_weights[child_idx].unwrap_or(0.0);
264 let allocated_height =
265 Px((remaining_height.0 as f32 * (child_weight / total_weight)) as i32);
266 let child_id = input.children_ids[child_idx];
267 let parent_offered_constraint_for_child = Constraint::new(
268 column_effective_constraint.width,
269 DimensionValue::Fixed(allocated_height),
270 );
271 (child_id, parent_offered_constraint_for_child)
272 })
273 .collect();
274
275 let children_results = input.measure_children(children_to_measure)?;
276
277 for &child_idx in weighted_indices {
278 let child_id = input.children_ids[child_idx];
279 if let Some(child_result) = children_results.get(&child_id) {
280 children_sizes[child_idx] = Some(*child_result);
281 *max_child_width = (*max_child_width).max(child_result.width);
282 }
283 }
284
285 Ok(())
286}
287
288fn calculate_final_column_height(
289 column_effective_constraint: &Constraint,
290 measured_children_height: Px,
291) -> Px {
292 match column_effective_constraint.height {
293 DimensionValue::Fixed(h) => h,
294 DimensionValue::Fill { min, max } => {
295 if let Some(max) = max {
296 if let Some(min) = min {
297 max.max(min)
298 } else {
299 max
300 }
301 } else {
302 panic!(
303 "Seems that you are trying to use Fill without max in a non-infinite parent constraint. This is not supported. Parent constraint: {column_effective_constraint:?}"
304 );
305 }
306 }
307 DimensionValue::Wrap { min, max } => {
308 let mut h = measured_children_height;
309 if let Some(min_h) = min {
310 h = h.max(min_h);
311 }
312 if let Some(max_h) = max {
313 h = h.min(max_h);
314 }
315 h
316 }
317 }
318}
319
320fn calculate_final_column_width(
321 column_effective_constraint: &Constraint,
322 max_child_width: Px,
323 parent_constraint: &Constraint,
324) -> Px {
325 match column_effective_constraint.width {
326 DimensionValue::Fixed(w) => w,
327 DimensionValue::Fill { min, max } => {
328 if let Some(max) = max {
329 if let Some(min) = min {
330 max.max(min)
331 } else {
332 max
333 }
334 } else {
335 panic!(
336 "Seems that you are trying to use Fill without max in a non-infinite parent constraint. This is not supported. Parent constraint: {parent_constraint:?}"
337 );
338 }
339 }
340 DimensionValue::Wrap { min, max } => {
341 let mut w = max_child_width;
342 if let Some(min_w) = min {
343 w = w.max(min_w);
344 }
345 if let Some(max_w) = max {
346 w = w.min(max_w);
347 }
348 w
349 }
350 }
351}
352
353fn measure_weighted_column(
356 input: &MeasureInput,
357 _args: &ColumnArgs,
358 child_weights: &[Option<f32>],
359 column_effective_constraint: &Constraint,
360 children_sizes: &mut [Option<ComputedData>],
361 max_child_width: &mut Px,
362) -> Result<(Px, Px, Px), MeasurementError> {
363 let available_height_for_children = column_effective_constraint.height.get_max().unwrap();
364
365 let (weighted_children_indices, unweighted_children_indices, total_weight_sum) =
366 classify_children(child_weights);
367
368 let total_height_of_unweighted_children = measure_unweighted_children_for_column(
369 input,
370 &unweighted_children_indices,
371 children_sizes,
372 max_child_width,
373 column_effective_constraint,
374 )?;
375
376 let remaining_height_for_weighted_children =
377 (available_height_for_children - total_height_of_unweighted_children).max(Px(0));
378
379 measure_weighted_children_for_column(
380 input,
381 &weighted_children_indices,
382 children_sizes,
383 max_child_width,
384 remaining_height_for_weighted_children,
385 total_weight_sum,
386 column_effective_constraint,
387 child_weights,
388 )?;
389
390 let total_measured_children_height: Px = children_sizes
391 .iter()
392 .filter_map(|s| s.as_ref().map(|s| s.height))
393 .fold(Px(0), |acc, h| acc + h);
394
395 let final_column_height =
396 calculate_final_column_height(column_effective_constraint, total_measured_children_height);
397 let final_column_width = calculate_final_column_width(
398 column_effective_constraint,
399 *max_child_width,
400 input.parent_constraint,
401 );
402
403 Ok((
404 final_column_width,
405 final_column_height,
406 total_measured_children_height,
407 ))
408}
409
410fn measure_unweighted_column(
411 input: &MeasureInput,
412 _args: &ColumnArgs,
413 column_effective_constraint: &Constraint,
414 children_sizes: &mut [Option<ComputedData>],
415 max_child_width: &mut Px,
416) -> Result<(Px, Px, Px), MeasurementError> {
417 let n = children_sizes.len();
418 let mut total_children_measured_height = Px(0);
419
420 let parent_offered_constraint_for_child = Constraint::new(
421 column_effective_constraint.width,
422 DimensionValue::Wrap {
423 min: None,
424 max: column_effective_constraint.height.get_max(),
425 },
426 );
427
428 let children_to_measure: Vec<_> = input
429 .children_ids
430 .iter()
431 .map(|&child_id| (child_id, parent_offered_constraint_for_child))
432 .collect();
433
434 let children_results = input.measure_children(children_to_measure)?;
435
436 for (i, &child_id) in input.children_ids.iter().enumerate().take(n) {
437 if let Some(child_result) = children_results.get(&child_id) {
438 children_sizes[i] = Some(*child_result);
439 total_children_measured_height += child_result.height;
440 *max_child_width = (*max_child_width).max(child_result.width);
441 }
442 }
443
444 let final_column_height =
445 calculate_final_column_height(column_effective_constraint, total_children_measured_height);
446 let final_column_width = calculate_final_column_width(
447 column_effective_constraint,
448 *max_child_width,
449 input.parent_constraint,
450 );
451 Ok((
452 final_column_width,
453 final_column_height,
454 total_children_measured_height,
455 ))
456}
457
458fn place_children_with_alignment(args: &PlaceChildrenArgs) {
467 let (mut current_y, spacing_between_children) = calculate_main_axis_layout_for_column(
468 args.final_column_height,
469 args.total_children_height,
470 args.main_axis_alignment,
471 args.child_count,
472 );
473
474 for (i, child_size_opt) in args.children_sizes.iter().enumerate() {
475 if let Some(child_actual_size) = child_size_opt {
476 let child_id = args.children_ids[i];
477 let x_offset = calculate_cross_axis_offset_for_column(
478 child_actual_size,
479 args.final_column_width,
480 args.cross_axis_alignment,
481 );
482 place_node(
483 child_id,
484 PxPosition::new(x_offset, current_y),
485 args.metadatas,
486 );
487 current_y += child_actual_size.height;
488 if i < args.child_count - 1 {
489 current_y += spacing_between_children;
490 }
491 }
492 }
493}
494
495fn calculate_main_axis_layout_for_column(
496 final_column_height: Px,
497 total_children_height: Px,
498 main_axis_alignment: MainAxisAlignment,
499 child_count: usize,
500) -> (Px, Px) {
501 let available_space = (final_column_height - total_children_height).max(Px(0));
502 match main_axis_alignment {
503 MainAxisAlignment::Start => (Px(0), Px(0)),
504 MainAxisAlignment::Center => (available_space / 2, Px(0)),
505 MainAxisAlignment::End => (available_space, Px(0)),
506 MainAxisAlignment::SpaceEvenly => {
507 if child_count > 0 {
508 let s = available_space / (child_count as i32 + 1);
509 (s, s)
510 } else {
511 (Px(0), Px(0))
512 }
513 }
514 MainAxisAlignment::SpaceBetween => {
515 if child_count > 1 {
516 (Px(0), available_space / (child_count as i32 - 1))
517 } else if child_count == 1 {
518 (available_space / 2, Px(0))
519 } else {
520 (Px(0), Px(0))
521 }
522 }
523 MainAxisAlignment::SpaceAround => {
524 if child_count > 0 {
525 let s = available_space / (child_count as i32);
526 (s / 2, s)
527 } else {
528 (Px(0), Px(0))
529 }
530 }
531 }
532}
533
534fn calculate_cross_axis_offset_for_column(
535 child_actual_size: &ComputedData,
536 final_column_width: Px,
537 cross_axis_alignment: CrossAxisAlignment,
538) -> Px {
539 match cross_axis_alignment {
540 CrossAxisAlignment::Start => Px(0),
541 CrossAxisAlignment::Center => (final_column_width - child_actual_size.width).max(Px(0)) / 2,
542 CrossAxisAlignment::End => (final_column_width - child_actual_size.width).max(Px(0)),
543 CrossAxisAlignment::Stretch => Px(0),
544 }
545}