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