use crate::constraints::impl_constraints;
use crate::{
AxisAlignment, BoxConstraints, BoxSizing, GlobalId, IntrinsicSize, Layout, LayoutError,
LayoutIter, Padding, Position, Size,
};
#[derive(Default, Debug)]
pub struct HorizontalLayout {
id: GlobalId,
size: Size,
position: Position,
spacing: u32,
padding: Padding,
constraints: BoxConstraints,
intrinsic_size: IntrinsicSize,
main_axis_alignment: AxisAlignment,
cross_axis_alignment: AxisAlignment,
children: Vec<Box<dyn Layout>>,
errors: Vec<LayoutError>,
label: Option<String>,
}
impl HorizontalLayout {
pub fn new() -> Self {
Self::default()
}
pub fn set_id(mut self, id: GlobalId) -> Self {
self.id = id;
self
}
pub fn add_child(mut self, child: impl Layout + 'static) -> Self {
self.children.push(Box::new(child));
self
}
pub fn with_label(mut self, label: &str) -> Self {
self.label = Some(label.to_string());
self
}
pub fn add_children<I>(mut self, children: I) -> Self
where
I: IntoIterator<Item: Layout + 'static>,
{
for child in children {
self.children.push(Box::new(child));
}
self
}
pub fn add_boxed_children<I>(mut self, children: I) -> Self
where
I: IntoIterator<Item = Box<dyn Layout + 'static>>,
{
for child in children {
self.children.push(child);
}
self
}
pub fn padding(mut self, padding: Padding) -> Self {
self.padding = padding;
self
}
pub fn spacing(mut self, spacing: u32) -> Self {
self.spacing = spacing;
self
}
pub fn main_axis_alignment(mut self, main_axis_alignment: AxisAlignment) -> Self {
self.main_axis_alignment = main_axis_alignment;
self
}
pub fn cross_axis_alignment(mut self, cross_axis_alignment: AxisAlignment) -> Self {
self.cross_axis_alignment = cross_axis_alignment;
self
}
fn compute_children_min_size(&mut self) -> Size {
let mut sum = Size::default();
if self.children.is_empty() {
return sum;
}
let space_between = (self.children.len() - 1) as f32 * self.spacing as f32;
sum.width += space_between;
for child in self.children.iter_mut() {
let (min_width, min_height) = child.solve_min_constraints();
sum.width += min_width;
sum.height = sum.height.max(min_height);
}
sum.width += self.padding.horizontal_sum();
sum.height += self.padding.vertical_sum();
sum
}
fn fixed_size_sum(&self) -> Size {
let mut sum = Size::default();
for (i, child) in self.children.iter().enumerate() {
match child.get_intrinsic_size().width {
BoxSizing::Fixed(width) => {
sum.width += width;
}
BoxSizing::Shrink => {
sum.width += child.constraints().min_width.unwrap_or_default();
}
_ => {}
}
if let BoxSizing::Fixed(height) = child.get_intrinsic_size().height {
sum.height = sum.height.max(height);
}
if i != self.children.len() - 1 {
sum.width += self.spacing as f32;
}
}
sum
}
fn align_main_axis_start(&mut self) {
let mut x_pos = self.position.x;
x_pos += self.padding.left;
for child in &mut self.children {
child.set_x(x_pos);
x_pos += child.size().width + self.spacing as f32;
}
}
fn align_main_axis_center(&mut self) {
if self.children.is_empty() {
return;
}
let mut width_sum = self
.children
.iter()
.map(|child| child.size().width)
.sum::<f32>();
let space_between = self.spacing * (self.children.len() - 1) as u32;
width_sum += space_between as f32;
let mut center_start = self.position.x + (self.size.width - width_sum) / 2.0;
for child in &mut self.children {
child.set_x(center_start);
center_start += child.size().width + self.spacing as f32;
}
}
fn align_main_axis_end(&mut self) {
let mut x_pos = self.position.x + self.size.width;
x_pos -= self.padding.right;
for child in self.children.iter_mut().rev() {
x_pos -= child.size().width;
child.set_x(x_pos);
x_pos -= self.spacing as f32;
}
}
fn align_cross_axis_start(&mut self) {
let y = self.position.y + self.padding.top;
for child in &mut self.children {
child.set_y(y);
}
}
fn align_cross_axis_center(&mut self) {
for child in &mut self.children {
let y_pos = (self.size.height - child.size().height) / 2.0 + self.position.y;
child.set_y(y_pos);
}
}
fn align_cross_axis_end(&mut self) {
for child in &mut self.children {
child.set_y(self.position.y + self.size.height - self.padding.bottom);
}
}
fn flex_total(&self) -> u32 {
self.children
.iter()
.filter_map(|child| {
if let BoxSizing::Flex(factor) = child.get_intrinsic_size().width {
Some(factor)
} else {
None
}
})
.sum()
}
impl_constraints!();
}
impl Layout for HorizontalLayout {
fn label(&self) -> String {
self.label.clone().unwrap_or("HorizontalLayout".to_string())
}
fn id(&self) -> GlobalId {
self.id
}
fn set_x(&mut self, x: f32) {
self.position.x = x;
}
fn set_y(&mut self, y: f32) {
self.position.y = y;
}
fn size(&self) -> Size {
self.size
}
fn position(&self) -> Position {
self.position
}
fn children(&self) -> &[Box<dyn Layout>] {
self.children.as_slice()
}
fn constraints(&self) -> BoxConstraints {
self.constraints
}
fn get_intrinsic_size(&self) -> IntrinsicSize {
self.intrinsic_size
}
fn set_max_height(&mut self, height: f32) {
self.constraints.max_height = Some(height);
}
fn set_max_width(&mut self, width: f32) {
self.constraints.max_width = Some(width);
}
fn set_min_height(&mut self, height: f32) {
self.constraints.min_height = Some(height);
}
fn set_min_width(&mut self, width: f32) {
self.constraints.min_width = Some(width);
}
fn collect_errors(&mut self) -> Vec<LayoutError> {
self.errors
.drain(..)
.chain(
self.children
.iter_mut()
.flat_map(|child| child.collect_errors()),
)
.collect::<Vec<_>>()
}
fn iter(&self) -> LayoutIter<'_> {
LayoutIter { stack: vec![self] }
}
fn solve_min_constraints(&mut self) -> (f32, f32) {
let child_constraint_sum = self.compute_children_min_size();
match self.intrinsic_size.width {
BoxSizing::Fixed(width) => {
self.constraints.min_width = Some(width);
}
BoxSizing::Flex(_) | BoxSizing::Shrink => {
let min_width = self
.constraints
.min_width
.unwrap_or_default()
.max(child_constraint_sum.width);
self.constraints.min_width = Some(min_width);
}
}
match self.intrinsic_size.height {
BoxSizing::Fixed(height) => {
self.constraints.min_height = Some(height);
}
BoxSizing::Flex(_) | BoxSizing::Shrink => {
let min_height = self
.constraints
.min_height
.unwrap_or_default()
.max(child_constraint_sum.height);
self.constraints.min_height = Some(min_height);
}
}
(
self.constraints.min_width.unwrap_or_default(),
self.constraints.min_height.unwrap_or_default(),
)
}
fn solve_max_constraints(&mut self) {
let flex_total = self.flex_total();
let available_height = match self.intrinsic_size.height {
BoxSizing::Shrink => self.constraints.min_height.unwrap_or_default(),
BoxSizing::Fixed(_) | BoxSizing::Flex(_) => {
self.constraints.max_height.unwrap_or_default() - self.padding.vertical_sum()
}
};
let available_width = match self.intrinsic_size.width {
BoxSizing::Shrink => {
self.constraints.min_width.unwrap_or_default() - self.fixed_size_sum().width
}
BoxSizing::Fixed(_) | BoxSizing::Flex(_) => {
self.constraints.max_width.unwrap_or_default()
- self.padding.horizontal_sum() - self.fixed_size_sum().width }
};
let mut width = available_width;
for child in &mut self.children {
if child.constraints().max_width.is_none() {
match child.get_intrinsic_size().width {
BoxSizing::Flex(factor) => {
let grow_factor = factor as f32 / flex_total as f32;
child.set_max_width(grow_factor * available_width);
width -= child.constraints().max_width.unwrap_or_default();
}
BoxSizing::Fixed(width) => {
child.set_max_width(width);
}
BoxSizing::Shrink => {
child.set_max_width(child.constraints().min_width.unwrap_or_default());
}
}
} else {
width -= child.constraints().max_width.unwrap_or_default()
}
dbg!(width);
match child.get_intrinsic_size().height {
BoxSizing::Flex(_) => {
child.set_max_height(available_height);
}
BoxSizing::Fixed(height) => {
child.set_max_height(height);
}
BoxSizing::Shrink => {
child.set_max_height(child.constraints().min_height.unwrap_or_default());
}
}
child.solve_max_constraints();
}
}
fn update_size(&mut self) {
match self.intrinsic_size.width {
BoxSizing::Flex(_) => {
self.size.width = self.constraints.max_width.unwrap_or_default();
}
BoxSizing::Shrink => {
self.size.width = self.constraints.min_width.unwrap_or_default();
}
BoxSizing::Fixed(width) => {
self.size.width = width;
}
}
match self.intrinsic_size.height {
BoxSizing::Flex(_) => {
self.size.height = self.constraints.max_height.unwrap_or_default();
}
BoxSizing::Shrink => {
self.size.height = self.constraints.min_height.unwrap_or_default();
}
BoxSizing::Fixed(height) => {
self.size.height = height;
}
}
for child in &mut self.children {
child.update_size();
}
}
fn position_children(&mut self) {
match self.main_axis_alignment {
AxisAlignment::Start => self.align_main_axis_start(),
AxisAlignment::Center => self.align_main_axis_center(),
AxisAlignment::End => self.align_main_axis_end(),
}
match self.cross_axis_alignment {
AxisAlignment::Start => self.align_cross_axis_start(),
AxisAlignment::Center => self.align_cross_axis_center(),
AxisAlignment::End => self.align_cross_axis_end(),
}
for child in &mut self.children {
if child.position().x > self.position.x + self.size.width {
self.errors.push(LayoutError::OutOfBounds {
parent_id: self.id,
child_id: child.id().to_owned(),
});
}
child.position_children();
}
}
}
impl<I> From<I> for HorizontalLayout
where
I: IntoIterator<Item: Layout + 'static>,
{
fn from(iter: I) -> Self {
HorizontalLayout::new().add_children(iter)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{EmptyLayout, solve_layout};
#[test]
fn min_width_larger_than_content_width() {
let child = EmptyLayout::default().intrinsic_size(IntrinsicSize::fixed(20.0, 20.0));
let mut layout = HorizontalLayout::from([child]).min_width(200.0);
let (width, _) = layout.solve_min_constraints();
assert_eq!(width, 200.0);
}
#[test]
fn min_height_larger_than_content_width() {
let child = EmptyLayout::default().intrinsic_size(IntrinsicSize::fixed(20.0, 20.0));
let mut layout = HorizontalLayout::from([child]).min_height(200.0);
let (_, height) = layout.solve_min_constraints();
assert_eq!(height, 200.0);
}
#[test]
fn min_width_smaller_than_content_width() {
let child = EmptyLayout::default().intrinsic_size(IntrinsicSize::fixed(20.0, 20.0));
let mut layout = HorizontalLayout::from([child]).min_width(5.0);
let (width, _) = layout.solve_min_constraints();
assert_eq!(width, 20.0);
}
#[test]
fn min_height_smaller_than_content_width() {
let child = EmptyLayout::default().intrinsic_size(IntrinsicSize::fixed(20.0, 20.0));
let mut layout = HorizontalLayout::from([child]).min_height(5.0);
let (_, height) = layout.solve_min_constraints();
assert_eq!(height, 20.0);
}
#[test]
fn fixed_min_constraints() {
let mut layout = HorizontalLayout {
intrinsic_size: IntrinsicSize::fixed(20.0, 24.0),
..Default::default()
};
layout.solve_min_constraints();
assert_eq!(layout.constraints.min_width.unwrap_or_default(), 20.0);
assert_eq!(layout.constraints.min_height.unwrap_or_default(), 24.0);
}
#[test]
fn respect_child_max_width() {
let child = EmptyLayout::new()
.max_width(200.0)
.intrinsic_size(IntrinsicSize::fill());
let mut layout = HorizontalLayout::new().add_child(child);
layout.solve_max_constraints();
assert_eq!(layout.children[0].constraints().max_width.unwrap(), 200.0);
}
#[test]
fn compute_min_size_no_children() {
let mut layout = HorizontalLayout::new();
let size = layout.compute_children_min_size();
assert_eq!(size, Size::default());
}
#[test]
fn calculate_min_width() {
let widths: &[f32] = &[500.0, 200.0, 10.2, 20.2, 45.0];
let children: Vec<Box<dyn Layout>> = widths
.iter()
.map(|w| EmptyLayout::new().intrinsic_size(IntrinsicSize::fixed(*w, 0.0)))
.map(|l| Box::new(l) as Box<dyn Layout>)
.collect();
let spacing = 20;
let padding = Padding::new(24.0, 42.0, 24.0, 20.0);
let mut layout = HorizontalLayout {
children,
spacing,
padding,
..Default::default()
};
layout.solve_min_constraints();
let space_between = (widths.len() - 1) as f32 * spacing as f32;
let mut min_width = widths.iter().sum::<f32>();
min_width += space_between;
min_width += padding.horizontal_sum();
assert_eq!(layout.constraints.min_width.unwrap_or_default(), min_width);
}
#[test]
fn calculate_min_height() {
let heights: [f32; 5] = [500.0, 200.0, 10.2, 20.2, 45.0];
let children: Vec<Box<dyn Layout>> = heights
.iter()
.map(|h| EmptyLayout::new().intrinsic_size(IntrinsicSize::fixed(0.0, *h)))
.map(|l| Box::new(l) as Box<dyn Layout>)
.collect();
let spacing = 20;
let padding = Padding::new(24.0, 42.0, 24.0, 20.0);
let mut layout = HorizontalLayout {
children,
spacing,
padding,
..Default::default()
};
layout.solve_min_constraints();
let mut max_height = heights
.into_iter()
.max_by(|x, y| x.partial_cmp(y).unwrap())
.unwrap();
max_height += padding.vertical_sum();
assert_eq!(
layout.constraints.min_height.unwrap_or_default(),
max_height
);
}
#[test]
fn align_main_axis_center_no_children() {
let mut layout = HorizontalLayout::new();
layout.align_main_axis_center();
}
#[test]
fn align_main_axis_end() {
let mut child = Box::new(EmptyLayout::new());
child.size.width = 200.0;
let mut layout = HorizontalLayout {
children: vec![child],
main_axis_alignment: AxisAlignment::End,
..Default::default()
};
layout.size.width = 500.0;
layout.position.x = 50.0;
layout.align_main_axis_end();
let position = layout.children[0].position();
let right_edge = layout.size.width + layout.position.x;
assert_eq!(position.x, right_edge - 200.0);
}
#[test]
fn align_main_axis_end_include_padding() {
let mut child = Box::new(EmptyLayout::new());
child.size.width = 200.0;
let mut layout = HorizontalLayout {
children: vec![child],
padding: Padding::new(10.0, 50.0, 20.0, 24.0),
main_axis_alignment: AxisAlignment::End,
..Default::default()
};
layout.size.width = 500.0;
layout.align_main_axis_end();
let position = layout.children[0].position();
assert_eq!(position.x, 500.0 - 200.0 - 50.0);
}
#[test]
fn align_main_axis_end_multiple_children() {
let widths: &[f32] = &[500.0, 200.0, 10.2, 20.2, 45.0];
let children: Vec<Box<dyn Layout>> = widths
.iter()
.map(|w| {
let mut layout = EmptyLayout::new();
layout.size = Size::unit(*w);
layout
})
.map(|l| Box::new(l) as Box<dyn Layout>)
.collect();
let size = Size::new(200.0, 200.0);
let position = Position::new(200.0, 200.0);
let mut layout = HorizontalLayout {
children,
size,
position,
spacing: 20,
padding: Padding::all(24.0),
main_axis_alignment: AxisAlignment::End,
..Default::default()
};
layout.align_main_axis_end();
let mut right_edge = layout.size.width + layout.position.x;
right_edge -= layout.padding.right;
let mut iter = layout.iter();
iter.next();
let layouts = iter.collect::<Vec<_>>();
let mut x_pos = right_edge;
for (i, l) in layouts.iter().rev().enumerate() {
x_pos -= l.size().width;
assert_eq!(l.position().x, x_pos, "Failed on iteration {i}");
x_pos -= layout.spacing as f32;
}
}
#[test]
fn start_alignment() {
let window = Size::new(200.0, 200.0);
let padding = Padding::all(24.0);
let spacing = 10;
let child_1 = EmptyLayout::new().intrinsic_size(IntrinsicSize::fixed(240.0, 40.0));
let child_2 = EmptyLayout::new().intrinsic_size(IntrinsicSize {
width: BoxSizing::Fixed(20.0),
..Default::default()
});
let mut root = HorizontalLayout {
position: Position { x: 250.0, y: 10.0 },
spacing,
padding,
children: vec![Box::new(child_1), Box::new(child_2)],
..Default::default()
};
solve_layout(&mut root, window);
let mut child_1_pos = root.position;
child_1_pos.x += padding.left;
child_1_pos.y += padding.top;
let mut child_2_pos = child_1_pos;
child_2_pos.x += root.children[0].size().width + spacing as f32;
assert_eq!(root.children[0].position(), child_1_pos);
assert_eq!(root.children[1].position(), child_2_pos);
}
}