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