1use derive_builder::Builder;
7use tessera_ui::{
8 ComputedData, Constraint, DimensionValue, MeasureInput, MeasurementError, NodeId, Px,
9 PxPosition, tessera,
10};
11
12use crate::alignment::{CrossAxisAlignment, MainAxisAlignment};
13
14#[derive(Builder, Clone, Debug)]
16#[builder(pattern = "owned")]
17pub struct RowArgs {
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 RowArgs {
33 fn default() -> Self {
34 RowArgsBuilder::default()
35 .build()
36 .expect("builder construction failed")
37 }
38}
39
40pub struct RowScope<'a> {
42 child_closures: &'a mut Vec<Box<dyn FnOnce() + Send + Sync>>,
43 child_weights: &'a mut Vec<Option<f32>>,
44}
45
46impl<'a> RowScope<'a> {
47 pub fn child<F>(&mut self, child_closure: F)
49 where
50 F: FnOnce() + Send + Sync + 'static,
51 {
52 self.child_closures.push(Box::new(child_closure));
53 self.child_weights.push(None);
54 }
55
56 pub fn child_weighted<F>(&mut self, child_closure: F, weight: f32)
58 where
59 F: FnOnce() + Send + Sync + 'static,
60 {
61 self.child_closures.push(Box::new(child_closure));
62 self.child_weights.push(Some(weight));
63 }
64}
65
66struct PlaceChildrenArgs<'a> {
67 children_sizes: &'a [Option<ComputedData>],
68 children_ids: &'a [NodeId],
69 input: &'a MeasureInput<'a>,
70 final_row_width: Px,
71 final_row_height: Px,
72 total_children_width: Px,
73 main_axis_alignment: MainAxisAlignment,
74 cross_axis_alignment: CrossAxisAlignment,
75 child_count: usize,
76}
77
78struct MeasureWeightedChildrenArgs<'a> {
79 input: &'a MeasureInput<'a>,
80 weighted_indices: &'a [usize],
81 children_sizes: &'a mut [Option<ComputedData>],
82 max_child_height: &'a mut Px,
83 remaining_width: Px,
84 total_weight: f32,
85 row_effective_constraint: &'a Constraint,
86 child_weights: &'a [Option<f32>],
87}
88
89#[tessera]
118pub fn row<F>(args: RowArgs, scope_config: F)
119where
120 F: FnOnce(&mut RowScope),
121{
122 let mut child_closures: Vec<Box<dyn FnOnce() + Send + Sync>> = Vec::new();
123 let mut child_weights: Vec<Option<f32>> = Vec::new();
124
125 {
126 let mut scope = RowScope {
127 child_closures: &mut child_closures,
128 child_weights: &mut child_weights,
129 };
130 scope_config(&mut scope);
131 }
132
133 let n = child_closures.len();
134
135 measure(Box::new(
136 move |input| -> Result<ComputedData, MeasurementError> {
137 assert_eq!(
138 input.children_ids.len(),
139 n,
140 "Mismatch between children defined in scope and runtime children count"
141 );
142
143 let row_intrinsic_constraint = Constraint::new(args.width, args.height);
144 let row_effective_constraint = row_intrinsic_constraint.merge(input.parent_constraint);
145
146 let has_weighted_children = child_weights.iter().any(|w| w.unwrap_or(0.0) > 0.0);
147 let should_use_weight_for_width = has_weighted_children
148 && matches!(
149 row_effective_constraint.width,
150 DimensionValue::Fixed(_)
151 | DimensionValue::Fill { max: Some(_), .. }
152 | DimensionValue::Wrap { max: Some(_), .. }
153 );
154
155 if should_use_weight_for_width {
156 measure_weighted_row(input, &args, &child_weights, &row_effective_constraint, n)
157 } else {
158 measure_unweighted_row(input, &args, &row_effective_constraint, n)
159 }
160 },
161 ));
162
163 for child_closure in child_closures {
164 child_closure();
165 }
166}
167
168fn measure_weighted_row(
169 input: &MeasureInput,
170 args: &RowArgs,
171 child_weights: &[Option<f32>],
172 row_effective_constraint: &Constraint,
173 n: usize,
174) -> Result<ComputedData, MeasurementError> {
175 let mut children_sizes = vec![None; n];
180 let mut max_child_height = Px(0);
181 let available_width_for_children = row_effective_constraint
182 .width
183 .get_max()
184 .expect("Row width Fill expected with finite max constraint");
185
186 let (weighted_indices, unweighted_indices, total_weight) = classify_children(child_weights);
188
189 let total_width_of_unweighted_children = measure_unweighted_children(
190 input,
191 &unweighted_indices,
192 &mut children_sizes,
193 &mut max_child_height,
194 row_effective_constraint,
195 )?;
196
197 measure_weighted_children(&mut MeasureWeightedChildrenArgs {
198 input,
199 weighted_indices: &weighted_indices,
200 children_sizes: &mut children_sizes,
201 max_child_height: &mut max_child_height,
202 remaining_width: available_width_for_children - total_width_of_unweighted_children,
203 total_weight,
204 row_effective_constraint,
205 child_weights,
206 })?;
207
208 let final_row_width = available_width_for_children;
209 let final_row_height = calculate_final_row_height(row_effective_constraint, max_child_height);
210
211 let total_measured_children_width: Px = children_sizes
212 .iter()
213 .filter_map(|s| s.map(|s| s.width))
214 .fold(Px(0), |acc, w| acc + w);
215
216 place_children_with_alignment(&PlaceChildrenArgs {
217 children_sizes: &children_sizes,
218 children_ids: input.children_ids,
219 input,
220 final_row_width,
221 final_row_height,
222 total_children_width: total_measured_children_width,
223 main_axis_alignment: args.main_axis_alignment,
224 cross_axis_alignment: args.cross_axis_alignment,
225 child_count: n,
226 });
227
228 Ok(ComputedData {
229 width: final_row_width,
230 height: final_row_height,
231 })
232}
233
234fn measure_unweighted_row(
235 input: &MeasureInput,
236 args: &RowArgs,
237 row_effective_constraint: &Constraint,
238 n: usize,
239) -> Result<ComputedData, MeasurementError> {
240 let mut children_sizes = vec![None; n];
241 let mut total_children_measured_width = Px(0);
242 let mut max_child_height = Px(0);
243
244 let parent_offered_constraint_for_child = Constraint::new(
245 match row_effective_constraint.width {
246 DimensionValue::Fixed(v) => DimensionValue::Wrap {
247 min: None,
248 max: Some(v),
249 },
250 DimensionValue::Fill { max, .. } => DimensionValue::Wrap { min: None, max },
251 DimensionValue::Wrap { max, .. } => DimensionValue::Wrap { min: None, max },
252 },
253 row_effective_constraint.height,
254 );
255
256 let children_to_measure: Vec<_> = input
257 .children_ids
258 .iter()
259 .map(|&child_id| (child_id, parent_offered_constraint_for_child))
260 .collect();
261
262 let children_results = input.measure_children(children_to_measure)?;
263
264 for (i, &child_id) in input.children_ids.iter().enumerate().take(n) {
265 if let Some(child_result) = children_results.get(&child_id) {
266 children_sizes[i] = Some(*child_result);
267 total_children_measured_width += child_result.width;
268 max_child_height = max_child_height.max(child_result.height);
269 }
270 }
271
272 let final_row_width =
273 calculate_final_row_width(row_effective_constraint, total_children_measured_width);
274 let final_row_height = calculate_final_row_height(row_effective_constraint, max_child_height);
275
276 place_children_with_alignment(&PlaceChildrenArgs {
277 children_sizes: &children_sizes,
278 children_ids: input.children_ids,
279 input,
280 final_row_width,
281 final_row_height,
282 total_children_width: total_children_measured_width,
283 main_axis_alignment: args.main_axis_alignment,
284 cross_axis_alignment: args.cross_axis_alignment,
285 child_count: n,
286 });
287
288 Ok(ComputedData {
289 width: final_row_width,
290 height: final_row_height,
291 })
292}
293
294fn classify_children(child_weights: &[Option<f32>]) -> (Vec<usize>, Vec<usize>, f32) {
295 let mut weighted_indices = Vec::new();
298 let mut unweighted_indices = Vec::new();
299 let mut total_weight = 0.0;
300
301 for (i, weight) in child_weights.iter().enumerate() {
302 if let Some(w) = weight {
303 if *w > 0.0 {
304 weighted_indices.push(i);
305 total_weight += w;
306 } else {
307 unweighted_indices.push(i);
309 }
310 } else {
311 unweighted_indices.push(i);
312 }
313 }
314 (weighted_indices, unweighted_indices, total_weight)
315}
316
317fn measure_unweighted_children(
318 input: &MeasureInput,
319 unweighted_indices: &[usize],
320 children_sizes: &mut [Option<ComputedData>],
321 max_child_height: &mut Px,
322 row_effective_constraint: &Constraint,
323) -> Result<Px, MeasurementError> {
324 let mut total_width = Px(0);
325
326 let parent_offered_constraint_for_child = Constraint::new(
327 DimensionValue::Wrap {
328 min: None,
329 max: row_effective_constraint.width.get_max(),
330 },
331 row_effective_constraint.height,
332 );
333
334 let children_to_measure: Vec<_> = unweighted_indices
335 .iter()
336 .map(|&child_idx| {
337 (
338 input.children_ids[child_idx],
339 parent_offered_constraint_for_child,
340 )
341 })
342 .collect();
343
344 let children_results = input.measure_children(children_to_measure)?;
345
346 for &child_idx in unweighted_indices {
347 let child_id = input.children_ids[child_idx];
348 if let Some(child_result) = children_results.get(&child_id) {
349 children_sizes[child_idx] = Some(*child_result);
350 total_width += child_result.width;
351 *max_child_height = (*max_child_height).max(child_result.height);
352 }
353 }
354
355 Ok(total_width)
356}
357
358fn measure_weighted_children(
359 args: &mut MeasureWeightedChildrenArgs,
360) -> Result<(), MeasurementError> {
361 if args.total_weight <= 0.0 {
362 return Ok(());
363 }
364
365 let children_to_measure: Vec<_> = args
366 .weighted_indices
367 .iter()
368 .map(|&child_idx| {
369 let child_weight = args.child_weights[child_idx].unwrap_or(0.0);
370 let allocated_width =
371 Px((args.remaining_width.0 as f32 * (child_weight / args.total_weight)) as i32);
372 let child_id = args.input.children_ids[child_idx];
373 let parent_offered_constraint_for_child = Constraint::new(
374 DimensionValue::Fixed(allocated_width),
375 args.row_effective_constraint.height,
376 );
377 (child_id, parent_offered_constraint_for_child)
378 })
379 .collect();
380
381 let children_results = args.input.measure_children(children_to_measure)?;
382
383 for &child_idx in args.weighted_indices {
384 let child_id = args.input.children_ids[child_idx];
385 if let Some(child_result) = children_results.get(&child_id) {
386 args.children_sizes[child_idx] = Some(*child_result);
387 *args.max_child_height = (*args.max_child_height).max(child_result.height);
388 }
389 }
390
391 Ok(())
392}
393
394fn calculate_final_row_width(
395 row_effective_constraint: &Constraint,
396 total_children_measured_width: Px,
397) -> Px {
398 match row_effective_constraint.width {
403 DimensionValue::Fixed(w) => w,
404 DimensionValue::Fill { min, max } => {
405 if let Some(max) = max {
406 let w = max;
407 if let Some(min) = min { w.max(min) } else { w }
408 } else {
409 panic!(
410 "Seem that you are using Fill without max constraint, which is not supported in Row width."
411 );
412 }
413 }
414 DimensionValue::Wrap { min, max } => {
415 let mut w = total_children_measured_width;
416 if let Some(min_w) = min {
417 w = w.max(min_w);
418 }
419 if let Some(max_w) = max {
420 w = w.min(max_w);
421 }
422 w
423 }
424 }
425}
426
427fn calculate_final_row_height(row_effective_constraint: &Constraint, max_child_height: Px) -> Px {
428 match row_effective_constraint.height {
433 DimensionValue::Fixed(h) => h,
434 DimensionValue::Fill { min, max } => {
435 if let Some(max_h) = max {
436 let h = max_h;
437 if let Some(min_h) = min {
438 h.max(min_h)
439 } else {
440 h
441 }
442 } else {
443 panic!(
444 "Seem that you are using Fill without max constraint, which is not supported in Row height."
445 );
446 }
447 }
448 DimensionValue::Wrap { min, max } => {
449 let mut h = max_child_height;
450 if let Some(min_h) = min {
451 h = h.max(min_h);
452 }
453 if let Some(max_h) = max {
454 h = h.min(max_h);
455 }
456 h
457 }
458 }
459}
460
461fn place_children_with_alignment(args: &PlaceChildrenArgs) {
462 let (mut current_x, spacing) = calculate_main_axis_layout(args);
467
468 for (i, child_size_opt) in args.children_sizes.iter().enumerate() {
469 if let Some(child_actual_size) = child_size_opt {
470 let child_id = args.children_ids[i];
471 let y_offset = calculate_cross_axis_offset(
472 child_actual_size,
473 args.final_row_height,
474 args.cross_axis_alignment,
475 );
476
477 args.input
478 .place_child(child_id, PxPosition::new(current_x, y_offset));
479 current_x += child_actual_size.width;
480 if i < args.child_count - 1 {
481 current_x += spacing;
482 }
483 }
484 }
485}
486
487fn calculate_main_axis_layout(args: &PlaceChildrenArgs) -> (Px, Px) {
488 let available_space = (args.final_row_width - args.total_children_width).max(Px(0));
491 match args.main_axis_alignment {
492 MainAxisAlignment::Start => (Px(0), Px(0)),
493 MainAxisAlignment::Center => (available_space / 2, Px(0)),
494 MainAxisAlignment::End => (available_space, Px(0)),
495 MainAxisAlignment::SpaceEvenly => calculate_space_evenly(available_space, args.child_count),
496 MainAxisAlignment::SpaceBetween => {
497 calculate_space_between(available_space, args.child_count)
498 }
499 MainAxisAlignment::SpaceAround => calculate_space_around(available_space, args.child_count),
500 }
501}
502
503fn calculate_space_evenly(available_space: Px, child_count: usize) -> (Px, Px) {
504 if child_count > 0 {
505 let s = available_space / (child_count as i32 + 1);
506 (s, s)
507 } else {
508 (Px(0), Px(0))
509 }
510}
511
512fn calculate_space_between(available_space: Px, child_count: usize) -> (Px, Px) {
513 if child_count > 1 {
514 (Px(0), available_space / (child_count as i32 - 1))
515 } else if child_count == 1 {
516 (available_space / 2, Px(0))
517 } else {
518 (Px(0), Px(0))
519 }
520}
521
522fn calculate_space_around(available_space: Px, child_count: usize) -> (Px, Px) {
523 if child_count > 0 {
524 let s = available_space / (child_count as i32);
525 (s / 2, s)
526 } else {
527 (Px(0), Px(0))
528 }
529}
530
531fn calculate_cross_axis_offset(
532 child_actual_size: &ComputedData,
533 final_row_height: Px,
534 cross_axis_alignment: CrossAxisAlignment,
535) -> Px {
536 match cross_axis_alignment {
542 CrossAxisAlignment::Start => Px(0),
543 CrossAxisAlignment::Center => (final_row_height - child_actual_size.height).max(Px(0)) / 2,
544 CrossAxisAlignment::End => (final_row_height - child_actual_size.height).max(Px(0)),
545 CrossAxisAlignment::Stretch => Px(0),
546 }
547}