use super::node::{ComputedLayout, LayoutNode};
use super::tree::LayoutTree;
use crate::style::{AlignItems, FlexDirection, JustifyContent, Size};
pub fn compute_flex(
tree: &mut LayoutTree,
node_id: u64,
available_width: u16,
available_height: u16,
) {
let node = match tree.get(node_id) {
Some(n) => n,
None => return,
};
let direction = node.flex.direction;
let justify = node.flex.justify_content;
let align = node.flex.align_items;
let gap = node.flex.main_gap();
let padding = node.spacing.padding;
let children: Vec<u64> = node.children.clone();
if children.is_empty() {
return;
}
let content_width = available_width
.saturating_sub(padding.left)
.saturating_sub(padding.right);
let content_height = available_height
.saturating_sub(padding.top)
.saturating_sub(padding.bottom);
let (main_size, cross_size) = match direction {
FlexDirection::Row => (content_width, content_height),
FlexDirection::Column => (content_height, content_width),
};
let total_gaps = gap.saturating_mul(children.len().saturating_sub(1) as u16);
let available_main = main_size.saturating_sub(total_gaps);
let mut child_main_sizes: Vec<u16> = vec![0; children.len()];
let mut child_flex_grows: Vec<f32> = vec![0.0; children.len()];
let mut total_fixed: u16 = 0;
let mut auto_count: usize = 0;
let mut total_grow: f32 = 0.0;
for (i, &child_id) in children.iter().enumerate() {
let child = match tree.get(child_id) {
Some(c) => c,
None => continue,
};
let grow = child.flex.flex_grow;
child_flex_grows[i] = grow;
total_grow += grow;
let size_prop = match direction {
FlexDirection::Row => child.sizing.width,
FlexDirection::Column => child.sizing.height,
};
match size_prop {
Size::Fixed(v) => {
let v = apply_main_constraints(child, direction, v, available_main);
child_main_sizes[i] = v;
total_fixed = total_fixed.saturating_add(v);
}
Size::Percent(pct) => {
let v = ((available_main as f32) * pct / 100.0).clamp(0.0, u16::MAX as f32) as u16;
let v = apply_main_constraints(child, direction, v, available_main);
child_main_sizes[i] = v;
total_fixed = total_fixed.saturating_add(v);
}
Size::Auto => {
auto_count += 1;
}
}
}
let remaining = available_main.saturating_sub(total_fixed);
if total_grow > 0.0 && remaining > 0 {
let mut grow_distributed: u16 = 0;
let grow_children: Vec<usize> = (0..children.len())
.filter(|&i| child_flex_grows[i] > 0.0)
.collect();
for (gi, &i) in grow_children.iter().enumerate() {
let child_id = children[i];
let child = match tree.get(child_id) {
Some(c) => c,
None => continue,
};
let share = if gi == grow_children.len() - 1 {
remaining.saturating_sub(grow_distributed)
} else {
((remaining as f32) * child_flex_grows[i] / total_grow).clamp(0.0, u16::MAX as f32)
as u16
};
let size = child_main_sizes[i].saturating_add(share);
let size = apply_main_constraints(child, direction, size, available_main);
child_main_sizes[i] = size;
grow_distributed = grow_distributed.saturating_add(share);
}
} else if auto_count > 0 && remaining > 0 {
let per_auto = remaining / auto_count as u16;
let extra = remaining % auto_count as u16;
let mut extra_given = 0u16;
for (i, &child_id) in children.iter().enumerate() {
let child = match tree.get(child_id) {
Some(c) => c,
None => continue,
};
let size_prop = match direction {
FlexDirection::Row => child.sizing.width,
FlexDirection::Column => child.sizing.height,
};
if matches!(size_prop, Size::Auto) {
let mut size = per_auto;
if extra_given < extra {
size += 1;
extra_given += 1;
}
let size = apply_main_constraints(child, direction, size, available_main);
child_main_sizes[i] = size;
}
}
}
let total_used: u16 = child_main_sizes
.iter()
.sum::<u16>()
.saturating_add(total_gaps);
let free_space = main_size.saturating_sub(total_used);
let (initial_offset, inter_gap) = compute_justify_offsets(justify, free_space, children.len());
let mut main_pos = initial_offset;
for (i, &child_id) in children.iter().enumerate() {
let child_main = child_main_sizes[i];
let child = match tree.get(child_id) {
Some(c) => c,
None => continue,
};
let cross_size_prop = match direction {
FlexDirection::Row => child.sizing.height,
FlexDirection::Column => child.sizing.width,
};
let child_cross = match cross_size_prop {
Size::Fixed(v) => v,
Size::Percent(pct) => {
((cross_size as f32) * pct / 100.0).clamp(0.0, u16::MAX as f32) as u16
}
Size::Auto => {
if align == AlignItems::Stretch {
cross_size
} else {
1
}
}
};
let child_cross = apply_cross_constraints(child, direction, child_cross, cross_size);
let cross_offset = compute_align_offset(align, cross_size, child_cross);
let (x, y, w, h) = match direction {
FlexDirection::Row => (
padding.left.saturating_add(main_pos),
padding.top.saturating_add(cross_offset),
child_main,
child_cross,
),
FlexDirection::Column => (
padding.left.saturating_add(cross_offset),
padding.top.saturating_add(main_pos),
child_cross,
child_main,
),
};
if let Some(child_mut) = tree.get_mut(child_id) {
child_mut.computed = ComputedLayout::new(x, y, w, h);
}
main_pos = main_pos.saturating_add(child_main).saturating_add(gap);
if i < children.len() - 1 {
main_pos = main_pos.saturating_add(inter_gap);
}
}
}
fn apply_main_constraints(
node: &LayoutNode,
direction: FlexDirection,
size: u16,
available: u16,
) -> u16 {
let (min_size, max_size) = match direction {
FlexDirection::Row => (node.sizing.min_width, node.sizing.max_width),
FlexDirection::Column => (node.sizing.min_height, node.sizing.max_height),
};
let min_val = resolve_constraint(min_size, available, 0);
let max_val = resolve_constraint(max_size, available, u16::MAX);
size.clamp(min_val, max_val)
}
fn apply_cross_constraints(
node: &LayoutNode,
direction: FlexDirection,
size: u16,
available: u16,
) -> u16 {
let (min_size, max_size) = match direction {
FlexDirection::Row => (node.sizing.min_height, node.sizing.max_height),
FlexDirection::Column => (node.sizing.min_width, node.sizing.max_width),
};
let min_val = resolve_constraint(min_size, available, 0);
let max_val = resolve_constraint(max_size, available, u16::MAX);
size.clamp(min_val, max_val)
}
fn resolve_constraint(size: Size, available: u16, default: u16) -> u16 {
match size {
Size::Auto => default,
Size::Fixed(v) => v,
Size::Percent(pct) => ((available as f32) * pct / 100.0).clamp(0.0, u16::MAX as f32) as u16,
}
}
fn compute_justify_offsets(
justify: JustifyContent,
free_space: u16,
child_count: usize,
) -> (u16, u16) {
match justify {
JustifyContent::Start => (0, 0),
JustifyContent::End => (free_space, 0),
JustifyContent::Center => (free_space / 2, 0),
JustifyContent::SpaceBetween => {
if child_count > 1 {
(0, free_space / (child_count - 1) as u16)
} else {
(0, 0)
}
}
JustifyContent::SpaceAround => {
if child_count > 0 {
let space = free_space / child_count as u16;
(space / 2, space)
} else {
(0, 0)
}
}
}
}
fn compute_align_offset(align: AlignItems, cross_size: u16, child_cross: u16) -> u16 {
match align {
AlignItems::Start => 0,
AlignItems::End => cross_size.saturating_sub(child_cross),
AlignItems::Center => cross_size.saturating_sub(child_cross) / 2,
AlignItems::Stretch => 0, }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::node::Edges;
fn setup_tree_with_parent_and_children(
parent_width: u16,
parent_height: u16,
child_widths: Vec<Size>,
direction: FlexDirection,
) -> (LayoutTree, u64, Vec<u64>) {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = direction;
parent.sizing.width = Size::Fixed(parent_width);
parent.sizing.height = Size::Fixed(parent_height);
let mut child_ids = Vec::new();
for (i, width) in child_widths.iter().enumerate() {
let mut child = LayoutNode::default();
child.id = (i + 2) as u64;
child.sizing.width = *width;
child.sizing.height = Size::Auto;
child_ids.push(child.id);
tree.insert(child);
}
parent.children = child_ids.clone();
tree.insert(parent);
tree.set_root(1);
(tree, 1, child_ids)
}
#[test]
fn test_flex_row_auto_distribution() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Auto, Size::Auto],
FlexDirection::Row,
);
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.width, 50);
assert_eq!(child1.computed.x, 0);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.width, 50);
assert_eq!(child2.computed.x, 50);
}
#[test]
fn test_flex_row_fixed_sizes() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Fixed(30), Size::Fixed(40)],
FlexDirection::Row,
);
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.width, 30);
assert_eq!(child1.computed.x, 0);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.width, 40);
assert_eq!(child2.computed.x, 30);
}
#[test]
fn test_flex_column() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
50,
100,
vec![Size::Auto, Size::Auto],
FlexDirection::Column,
);
for &id in &child_ids {
if let Some(node) = tree.get_mut(id) {
node.sizing.height = Size::Auto;
node.sizing.width = Size::Auto;
}
}
compute_flex(&mut tree, parent_id, 50, 100);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.height, 50);
assert_eq!(child1.computed.y, 0);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.height, 50);
assert_eq!(child2.computed.y, 50);
}
#[test]
fn test_flex_with_gap() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Auto, Size::Auto],
FlexDirection::Row,
);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.gap = 10;
}
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.width, 45);
assert_eq!(child1.computed.x, 0);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.width, 45);
assert_eq!(child2.computed.x, 55); }
#[test]
fn test_flex_justify_center() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Fixed(20), Size::Fixed(20)],
FlexDirection::Row,
);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.justify_content = JustifyContent::Center;
}
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.x, 30);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.x, 50);
}
#[test]
fn test_flex_justify_space_between() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Fixed(20), Size::Fixed(20)],
FlexDirection::Row,
);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.justify_content = JustifyContent::SpaceBetween;
}
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.x, 0);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.x, 80);
}
#[test]
fn test_flex_align_center() {
let (mut tree, parent_id, child_ids) =
setup_tree_with_parent_and_children(100, 50, vec![Size::Fixed(30)], FlexDirection::Row);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.align_items = AlignItems::Center;
}
if let Some(child) = tree.get_mut(child_ids[0]) {
child.sizing.height = Size::Fixed(20);
}
compute_flex(&mut tree, parent_id, 100, 50);
let child = tree.get(child_ids[0]).unwrap();
assert_eq!(child.computed.height, 20);
assert_eq!(child.computed.y, 15); }
#[test]
fn test_flex_with_padding() {
let (mut tree, parent_id, child_ids) =
setup_tree_with_parent_and_children(100, 50, vec![Size::Auto], FlexDirection::Row);
if let Some(parent) = tree.get_mut(parent_id) {
parent.spacing.padding = Edges {
top: 5,
right: 10,
bottom: 5,
left: 10,
};
}
compute_flex(&mut tree, parent_id, 100, 50);
let child = tree.get(child_ids[0]).unwrap();
assert_eq!(child.computed.width, 80);
assert_eq!(child.computed.x, 10); assert_eq!(child.computed.y, 5); }
#[test]
fn test_flex_justify_end() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Fixed(20), Size::Fixed(20)],
FlexDirection::Row,
);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.justify_content = JustifyContent::End;
}
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.x, 60);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.x, 80);
}
#[test]
fn test_flex_justify_space_around() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Fixed(20), Size::Fixed(20)],
FlexDirection::Row,
);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.justify_content = JustifyContent::SpaceAround;
}
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.x, 15);
}
#[test]
fn test_flex_align_end() {
let (mut tree, parent_id, child_ids) =
setup_tree_with_parent_and_children(100, 50, vec![Size::Fixed(30)], FlexDirection::Row);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.align_items = AlignItems::End;
}
if let Some(child) = tree.get_mut(child_ids[0]) {
child.sizing.height = Size::Fixed(20);
}
compute_flex(&mut tree, parent_id, 100, 50);
let child = tree.get(child_ids[0]).unwrap();
assert_eq!(child.computed.y, 30); }
#[test]
fn test_flex_align_stretch() {
let (mut tree, parent_id, child_ids) =
setup_tree_with_parent_and_children(100, 50, vec![Size::Fixed(30)], FlexDirection::Row);
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.align_items = AlignItems::Stretch;
}
if let Some(child) = tree.get_mut(child_ids[0]) {
child.sizing.height = Size::Auto;
}
compute_flex(&mut tree, parent_id, 100, 50);
let child = tree.get(child_ids[0]).unwrap();
assert_eq!(child.computed.height, 50); }
#[test]
fn test_flex_mixed_sizes() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
100,
50,
vec![Size::Fixed(20), Size::Percent(30.0), Size::Auto],
FlexDirection::Row,
);
compute_flex(&mut tree, parent_id, 100, 50);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.width, 20);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.width, 30);
let child3 = tree.get(child_ids[2]).unwrap();
assert_eq!(child3.computed.width, 50); }
#[test]
fn test_flex_min_max_main_axis() {
let (mut tree, parent_id, child_ids) =
setup_tree_with_parent_and_children(100, 50, vec![Size::Auto], FlexDirection::Row);
if let Some(child) = tree.get_mut(child_ids[0]) {
child.sizing.min_width = Size::Fixed(30);
child.sizing.max_width = Size::Fixed(60);
}
compute_flex(&mut tree, parent_id, 100, 50);
let child = tree.get(child_ids[0]).unwrap();
assert_eq!(child.computed.width, 60);
}
#[test]
fn test_flex_single_child() {
let (mut tree, parent_id, child_ids) =
setup_tree_with_parent_and_children(100, 50, vec![Size::Auto], FlexDirection::Row);
compute_flex(&mut tree, parent_id, 100, 50);
let child = tree.get(child_ids[0]).unwrap();
assert_eq!(child.computed.width, 100); assert_eq!(child.computed.x, 0);
}
#[test]
fn test_flex_column_with_gap() {
let (mut tree, parent_id, child_ids) = setup_tree_with_parent_and_children(
50,
100,
vec![Size::Auto, Size::Auto, Size::Auto],
FlexDirection::Column,
);
for &id in &child_ids {
if let Some(node) = tree.get_mut(id) {
node.sizing.height = Size::Auto;
node.sizing.width = Size::Auto;
}
}
if let Some(parent) = tree.get_mut(parent_id) {
parent.flex.gap = 5;
}
compute_flex(&mut tree, parent_id, 50, 100);
let child1 = tree.get(child_ids[0]).unwrap();
assert_eq!(child1.computed.height, 30);
assert_eq!(child1.computed.y, 0);
let child2 = tree.get(child_ids[1]).unwrap();
assert_eq!(child2.computed.y, 35);
let child3 = tree.get(child_ids[2]).unwrap();
assert_eq!(child3.computed.y, 70); }
#[test]
fn test_flex_empty_container() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.display = crate::style::Display::Flex;
parent.children = vec![];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 100, 50);
}
#[test]
fn test_flex_no_auto_children() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = FlexDirection::Row;
parent.sizing.width = Size::Fixed(100);
parent.sizing.height = Size::Fixed(50);
let mut child1 = LayoutNode::default();
child1.id = 2;
child1.sizing.width = Size::Fixed(30);
child1.sizing.height = Size::Auto;
let mut child2 = LayoutNode::default();
child2.id = 3;
child2.sizing.width = Size::Fixed(40);
child2.sizing.height = Size::Auto;
tree.insert(child1);
tree.insert(child2);
parent.children = vec![2, 3];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 100, 50);
let child1 = tree.get(2).unwrap();
let child2 = tree.get(3).unwrap();
assert_eq!(child1.computed.width, 30);
assert_eq!(child2.computed.width, 40);
}
#[test]
fn test_flex_all_percent_children() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = FlexDirection::Row;
parent.sizing.width = Size::Fixed(100);
parent.sizing.height = Size::Fixed(50);
let mut child1 = LayoutNode::default();
child1.id = 2;
child1.sizing.width = Size::Percent(30.0);
child1.sizing.height = Size::Auto;
let mut child2 = LayoutNode::default();
child2.id = 3;
child2.sizing.width = Size::Percent(50.0);
child2.sizing.height = Size::Auto;
tree.insert(child1);
tree.insert(child2);
parent.children = vec![2, 3];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 100, 50);
let child1 = tree.get(2).unwrap();
let child2 = tree.get(3).unwrap();
assert_eq!(child1.computed.width, 30);
assert_eq!(child2.computed.width, 50);
}
#[test]
fn test_flex_zero_width_container() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = FlexDirection::Row;
parent.sizing.width = Size::Fixed(0);
parent.sizing.height = Size::Fixed(50);
let mut child = LayoutNode::default();
child.id = 2;
child.sizing.width = Size::Auto;
child.sizing.height = Size::Auto;
tree.insert(child);
parent.children = vec![2];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 0, 50);
let child = tree.get(2).unwrap();
assert_eq!(child.computed.width, 0);
}
#[test]
fn test_flex_justify_space_between_single_child() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = FlexDirection::Row;
parent.flex.justify_content = JustifyContent::SpaceBetween;
parent.sizing.width = Size::Fixed(100);
parent.sizing.height = Size::Fixed(50);
let mut child = LayoutNode::default();
child.id = 2;
child.sizing.width = Size::Fixed(30);
child.sizing.height = Size::Auto;
tree.insert(child);
parent.children = vec![2];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 100, 50);
let child = tree.get(2).unwrap();
assert_eq!(child.computed.x, 0);
}
#[test]
fn test_flex_justify_space_around_empty() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = FlexDirection::Row;
parent.flex.justify_content = JustifyContent::SpaceAround;
parent.sizing.width = Size::Fixed(100);
parent.sizing.height = Size::Fixed(50);
parent.children = vec![];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 100, 50);
}
#[test]
fn test_flex_gap_with_empty() {
let mut tree = LayoutTree::new();
let mut parent = LayoutNode::default();
parent.id = 1;
parent.flex.direction = FlexDirection::Row;
parent.flex.gap = 10;
parent.sizing.width = Size::Fixed(100);
parent.sizing.height = Size::Fixed(50);
parent.children = vec![];
tree.insert(parent);
tree.set_root(1);
compute_flex(&mut tree, 1, 100, 50);
}
}