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