use derive_setters::Setters;
use tessera_ui::{
ComputedData, Constraint, DimensionValue, LayoutInput, LayoutOutput, LayoutSpec,
MeasurementError, Modifier, NodeId, Px, PxPosition, tessera,
};
use crate::{
alignment::{CrossAxisAlignment, MainAxisAlignment},
modifier::ModifierExt as _,
};
#[derive(Clone, Debug, Setters)]
pub struct RowArgs {
pub modifier: Modifier,
pub main_axis_alignment: MainAxisAlignment,
pub cross_axis_alignment: CrossAxisAlignment,
}
impl Default for RowArgs {
fn default() -> Self {
Self {
modifier: Modifier::new()
.constrain(Some(DimensionValue::WRAP), Some(DimensionValue::WRAP)),
main_axis_alignment: MainAxisAlignment::Start,
cross_axis_alignment: CrossAxisAlignment::Start,
}
}
}
pub struct RowScope<'a> {
child_closures: &'a mut Vec<Box<dyn FnOnce() + Send + Sync>>,
child_weights: &'a mut Vec<Option<f32>>,
}
impl<'a> RowScope<'a> {
pub fn child<F>(&mut self, child_closure: F)
where
F: FnOnce() + Send + Sync + 'static,
{
self.child_closures.push(Box::new(child_closure));
self.child_weights.push(None);
}
pub fn child_weighted<F>(&mut self, child_closure: F, weight: f32)
where
F: FnOnce() + Send + Sync + 'static,
{
self.child_closures.push(Box::new(child_closure));
self.child_weights.push(Some(weight));
}
}
struct PlaceChildrenArgs<'a> {
children_sizes: &'a [Option<ComputedData>],
children_ids: &'a [NodeId],
final_row_width: Px,
final_row_height: Px,
total_children_width: Px,
main_axis_alignment: MainAxisAlignment,
cross_axis_alignment: CrossAxisAlignment,
child_count: usize,
}
struct MeasureWeightedChildrenArgs<'a> {
input: &'a LayoutInput<'a>,
weighted_indices: &'a [usize],
children_sizes: &'a mut [Option<ComputedData>],
max_child_height: &'a mut Px,
remaining_width: Px,
total_weight: f32,
row_effective_constraint: &'a Constraint,
child_weights: &'a [Option<f32>],
}
#[derive(Clone, PartialEq)]
struct RowLayout {
main_axis_alignment: MainAxisAlignment,
cross_axis_alignment: CrossAxisAlignment,
child_weights: Vec<Option<f32>>,
}
impl LayoutSpec for RowLayout {
fn measure(
&self,
input: &LayoutInput<'_>,
output: &mut LayoutOutput<'_>,
) -> Result<ComputedData, MeasurementError> {
let n = self.child_weights.len();
assert_eq!(
input.children_ids().len(),
n,
"Mismatch between children defined in scope and runtime children count"
);
let row_effective_constraint = Constraint::new(
input.parent_constraint().width(),
input.parent_constraint().height(),
);
let has_weighted_children = self.child_weights.iter().any(|w| w.unwrap_or(0.0) > 0.0);
let should_use_weight_for_width = has_weighted_children
&& matches!(
row_effective_constraint.width,
DimensionValue::Fixed(_)
| DimensionValue::Fill { max: Some(_), .. }
| DimensionValue::Wrap { max: Some(_), .. }
);
if should_use_weight_for_width {
measure_weighted_row(
input,
output,
self.main_axis_alignment,
self.cross_axis_alignment,
&self.child_weights,
&row_effective_constraint,
)
} else {
measure_unweighted_row(
input,
output,
self.main_axis_alignment,
self.cross_axis_alignment,
&row_effective_constraint,
)
}
}
}
#[tessera]
pub fn row<F>(args: RowArgs, scope_config: F)
where
F: FnOnce(&mut RowScope),
{
let modifier = args.modifier;
let mut child_closures: Vec<Box<dyn FnOnce() + Send + Sync>> = Vec::new();
let mut child_weights: Vec<Option<f32>> = Vec::new();
{
let mut scope = RowScope {
child_closures: &mut child_closures,
child_weights: &mut child_weights,
};
scope_config(&mut scope);
}
modifier.run(move || row_inner(args, child_closures, child_weights));
}
#[tessera]
fn row_inner(
args: RowArgs,
child_closures: Vec<Box<dyn FnOnce() + Send + Sync>>,
child_weights: Vec<Option<f32>>,
) {
layout(RowLayout {
main_axis_alignment: args.main_axis_alignment,
cross_axis_alignment: args.cross_axis_alignment,
child_weights,
});
for child_closure in child_closures {
child_closure();
}
}
fn measure_weighted_row(
input: &LayoutInput<'_>,
output: &mut LayoutOutput<'_>,
main_axis_alignment: MainAxisAlignment,
cross_axis_alignment: CrossAxisAlignment,
child_weights: &[Option<f32>],
row_effective_constraint: &Constraint,
) -> Result<ComputedData, MeasurementError> {
let mut children_sizes = vec![None; child_weights.len()];
let mut max_child_height = Px(0);
let available_width_for_children = row_effective_constraint
.width
.get_max()
.expect("Row width Fill expected with finite max constraint");
let (weighted_indices, unweighted_indices, total_weight) = classify_children(child_weights);
let total_width_of_unweighted_children = measure_unweighted_children(
input,
&unweighted_indices,
&mut children_sizes,
&mut max_child_height,
row_effective_constraint,
)?;
measure_weighted_children(&mut MeasureWeightedChildrenArgs {
input,
weighted_indices: &weighted_indices,
children_sizes: &mut children_sizes,
max_child_height: &mut max_child_height,
remaining_width: available_width_for_children - total_width_of_unweighted_children,
total_weight,
row_effective_constraint,
child_weights,
})?;
let final_row_width = available_width_for_children;
let final_row_height = calculate_final_row_height(row_effective_constraint, max_child_height);
let total_measured_children_width: Px = children_sizes
.iter()
.filter_map(|s| s.map(|s| s.width))
.fold(Px(0), |acc, w| acc + w);
place_children_with_alignment(
&PlaceChildrenArgs {
children_sizes: &children_sizes,
children_ids: input.children_ids(),
final_row_width,
final_row_height,
total_children_width: total_measured_children_width,
main_axis_alignment,
cross_axis_alignment,
child_count: child_weights.len(),
},
output,
);
Ok(ComputedData {
width: final_row_width,
height: final_row_height,
})
}
fn measure_unweighted_row(
input: &LayoutInput<'_>,
output: &mut LayoutOutput<'_>,
main_axis_alignment: MainAxisAlignment,
cross_axis_alignment: CrossAxisAlignment,
row_effective_constraint: &Constraint,
) -> Result<ComputedData, MeasurementError> {
let mut children_sizes = vec![None; input.children_ids().len()];
let mut total_children_measured_width = Px(0);
let mut max_child_height = Px(0);
let parent_offered_constraint_for_child = Constraint::new(
match row_effective_constraint.width {
DimensionValue::Fixed(v) => DimensionValue::Wrap {
min: None,
max: Some(v),
},
DimensionValue::Fill { max, .. } => DimensionValue::Wrap { min: None, max },
DimensionValue::Wrap { max, .. } => DimensionValue::Wrap { min: None, max },
},
row_effective_constraint.height,
);
let children_to_measure: Vec<_> = input
.children_ids()
.iter()
.map(|&child_id| (child_id, parent_offered_constraint_for_child))
.collect();
let children_results = input.measure_children(children_to_measure)?;
for (i, &child_id) in input.children_ids().iter().enumerate() {
if let Some(child_result) = children_results.get(&child_id) {
children_sizes[i] = Some(*child_result);
total_children_measured_width += child_result.width;
max_child_height = max_child_height.max(child_result.height);
}
}
let final_row_width =
calculate_final_row_width(row_effective_constraint, total_children_measured_width);
let final_row_height = calculate_final_row_height(row_effective_constraint, max_child_height);
place_children_with_alignment(
&PlaceChildrenArgs {
children_sizes: &children_sizes,
children_ids: input.children_ids(),
final_row_width,
final_row_height,
total_children_width: total_children_measured_width,
main_axis_alignment,
cross_axis_alignment,
child_count: children_sizes.len(),
},
output,
);
Ok(ComputedData {
width: final_row_width,
height: final_row_height,
})
}
fn classify_children(child_weights: &[Option<f32>]) -> (Vec<usize>, Vec<usize>, f32) {
let mut weighted_indices = Vec::new();
let mut unweighted_indices = Vec::new();
let mut total_weight = 0.0;
for (i, weight) in child_weights.iter().enumerate() {
if let Some(w) = weight {
if *w > 0.0 {
weighted_indices.push(i);
total_weight += w;
} else {
unweighted_indices.push(i);
}
} else {
unweighted_indices.push(i);
}
}
(weighted_indices, unweighted_indices, total_weight)
}
fn measure_unweighted_children(
input: &LayoutInput<'_>,
unweighted_indices: &[usize],
children_sizes: &mut [Option<ComputedData>],
max_child_height: &mut Px,
row_effective_constraint: &Constraint,
) -> Result<Px, MeasurementError> {
let mut total_width = Px(0);
let parent_offered_constraint_for_child = Constraint::new(
DimensionValue::Wrap {
min: None,
max: row_effective_constraint.width.get_max(),
},
row_effective_constraint.height,
);
let children_to_measure: Vec<_> = unweighted_indices
.iter()
.map(|&child_idx| {
(
input.children_ids()[child_idx],
parent_offered_constraint_for_child,
)
})
.collect();
let children_results = input.measure_children(children_to_measure)?;
for &child_idx in unweighted_indices {
let child_id = input.children_ids()[child_idx];
if let Some(child_result) = children_results.get(&child_id) {
children_sizes[child_idx] = Some(*child_result);
total_width += child_result.width;
*max_child_height = (*max_child_height).max(child_result.height);
}
}
Ok(total_width)
}
fn measure_weighted_children(
args: &mut MeasureWeightedChildrenArgs,
) -> Result<(), MeasurementError> {
if args.total_weight <= 0.0 {
return Ok(());
}
let children_to_measure: Vec<_> = args
.weighted_indices
.iter()
.map(|&child_idx| {
let child_weight = args.child_weights[child_idx].unwrap_or(0.0);
let allocated_width =
Px((args.remaining_width.0 as f32 * (child_weight / args.total_weight)) as i32);
let child_id = args.input.children_ids()[child_idx];
let parent_offered_constraint_for_child = Constraint::new(
DimensionValue::Fixed(allocated_width),
args.row_effective_constraint.height,
);
(child_id, parent_offered_constraint_for_child)
})
.collect();
let children_results = args.input.measure_children(children_to_measure)?;
for &child_idx in args.weighted_indices {
let child_id = args.input.children_ids()[child_idx];
if let Some(child_result) = children_results.get(&child_id) {
args.children_sizes[child_idx] = Some(*child_result);
*args.max_child_height = (*args.max_child_height).max(child_result.height);
}
}
Ok(())
}
fn calculate_final_row_width(
row_effective_constraint: &Constraint,
total_children_measured_width: Px,
) -> Px {
match row_effective_constraint.width {
DimensionValue::Fixed(w) => w,
DimensionValue::Fill { min, max } => {
if let Some(max) = max {
let w = max;
if let Some(min) = min { w.max(min) } else { w }
} else {
panic!(
"Seem that you are using Fill without max constraint, which is not supported in Row width."
);
}
}
DimensionValue::Wrap { min, max } => {
let mut w = total_children_measured_width;
if let Some(min_w) = min {
w = w.max(min_w);
}
if let Some(max_w) = max {
w = w.min(max_w);
}
w
}
}
}
fn calculate_final_row_height(row_effective_constraint: &Constraint, max_child_height: Px) -> Px {
match row_effective_constraint.height {
DimensionValue::Fixed(h) => h,
DimensionValue::Fill { min, max } => {
if let Some(max_h) = max {
let h = max_h;
if let Some(min_h) = min {
h.max(min_h)
} else {
h
}
} else {
panic!(
"Seem that you are using Fill without max constraint, which is not supported in Row height."
);
}
}
DimensionValue::Wrap { min, max } => {
let mut h = max_child_height;
if let Some(min_h) = min {
h = h.max(min_h);
}
if let Some(max_h) = max {
h = h.min(max_h);
}
h
}
}
}
fn place_children_with_alignment(args: &PlaceChildrenArgs, output: &mut LayoutOutput<'_>) {
let (mut current_x, spacing) = calculate_main_axis_layout(args);
for (i, child_size_opt) in args.children_sizes.iter().enumerate() {
if let Some(child_actual_size) = child_size_opt {
let child_id = args.children_ids[i];
let y_offset = calculate_cross_axis_offset(
child_actual_size,
args.final_row_height,
args.cross_axis_alignment,
);
output.place_child(child_id, PxPosition::new(current_x, y_offset));
current_x += child_actual_size.width;
if i < args.child_count - 1 {
current_x += spacing;
}
}
}
}
fn calculate_main_axis_layout(args: &PlaceChildrenArgs) -> (Px, Px) {
let available_space = (args.final_row_width - args.total_children_width).max(Px(0));
match args.main_axis_alignment {
MainAxisAlignment::Start => (Px(0), Px(0)),
MainAxisAlignment::Center => (available_space / 2, Px(0)),
MainAxisAlignment::End => (available_space, Px(0)),
MainAxisAlignment::SpaceEvenly => calculate_space_evenly(available_space, args.child_count),
MainAxisAlignment::SpaceBetween => {
calculate_space_between(available_space, args.child_count)
}
MainAxisAlignment::SpaceAround => calculate_space_around(available_space, args.child_count),
}
}
fn calculate_space_evenly(available_space: Px, child_count: usize) -> (Px, Px) {
if child_count > 0 {
let s = available_space / (child_count as i32 + 1);
(s, s)
} else {
(Px(0), Px(0))
}
}
fn calculate_space_between(available_space: Px, child_count: usize) -> (Px, Px) {
if child_count > 1 {
(Px(0), available_space / (child_count as i32 - 1))
} else if child_count == 1 {
(available_space / 2, Px(0))
} else {
(Px(0), Px(0))
}
}
fn calculate_space_around(available_space: Px, child_count: usize) -> (Px, Px) {
if child_count > 0 {
let s = available_space / (child_count as i32);
(s / 2, s)
} else {
(Px(0), Px(0))
}
}
fn calculate_cross_axis_offset(
child_actual_size: &ComputedData,
final_row_height: Px,
cross_axis_alignment: CrossAxisAlignment,
) -> Px {
match cross_axis_alignment {
CrossAxisAlignment::Start => Px(0),
CrossAxisAlignment::Center => (final_row_height - child_actual_size.height).max(Px(0)) / 2,
CrossAxisAlignment::End => (final_row_height - child_actual_size.height).max(Px(0)),
CrossAxisAlignment::Stretch => Px(0),
}
}