mod constraint;
mod flex;
mod grid;
mod responsive;
mod scroll;
pub use constraint::{ConstraintLayout, ConstraintRule, MinMax};
pub use flex::{Align, CrossAlign, FlexItem, FlexLayout, FlexWrap, JustifyContent};
pub use grid::{GridItem, GridLayout, GridSpan};
pub use responsive::{Breakpoint, ResponsiveLayout};
pub use scroll::{ScrollContainer, ScrollDirection};
use crate::core::Rect;
#[derive(Debug, Clone, Copy)]
pub struct SizeConstraints {
pub min_width: f32,
pub min_height: f32,
pub max_width: f32,
pub max_height: f32,
}
impl SizeConstraints {
pub fn unconstrained() -> Self {
Self {
min_width: 0.0,
min_height: 0.0,
max_width: f32::INFINITY,
max_height: f32::INFINITY,
}
}
pub fn fixed(width: f32, height: f32) -> Self {
Self {
min_width: width,
min_height: height,
max_width: width,
max_height: height,
}
}
pub fn from_rect(area: Rect) -> Self {
Self {
min_width: 0.0,
min_height: 0.0,
max_width: area.width,
max_height: area.height,
}
}
pub fn clamp(&self, width: f32, height: f32) -> (f32, f32) {
(
width.clamp(self.min_width, self.max_width),
height.clamp(self.min_height, self.max_height),
)
}
}
impl Default for SizeConstraints {
fn default() -> Self {
Self::unconstrained()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct DesiredSize {
pub width: f32,
pub height: f32,
}
impl DesiredSize {
pub fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
pub fn zero() -> Self {
Self {
width: 0.0,
height: 0.0,
}
}
}
pub trait Layout {
fn measure(&self, children: &[DesiredSize], constraints: SizeConstraints) -> DesiredSize;
fn arrange(&self, children: &[DesiredSize], area: Rect) -> Vec<Rect>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn size_constraints_unconstrained() {
let c = SizeConstraints::unconstrained();
assert_eq!(c.min_width, 0.0);
assert!(c.max_width.is_infinite());
}
#[test]
fn size_constraints_fixed() {
let c = SizeConstraints::fixed(100.0, 50.0);
assert_eq!(c.min_width, 100.0);
assert_eq!(c.max_width, 100.0);
assert_eq!(c.min_height, 50.0);
assert_eq!(c.max_height, 50.0);
}
#[test]
fn size_constraints_clamp() {
let c = SizeConstraints {
min_width: 10.0,
min_height: 10.0,
max_width: 100.0,
max_height: 100.0,
};
assert_eq!(c.clamp(5.0, 200.0), (10.0, 100.0));
assert_eq!(c.clamp(50.0, 50.0), (50.0, 50.0));
}
#[test]
fn desired_size_zero() {
let s = DesiredSize::zero();
assert_eq!(s.width, 0.0);
assert_eq!(s.height, 0.0);
}
#[test]
fn size_constraints_from_rect() {
let r = Rect::new(10.0, 20.0, 300.0, 400.0);
let c = SizeConstraints::from_rect(r);
assert_eq!(c.max_width, 300.0);
assert_eq!(c.max_height, 400.0);
}
#[test]
fn flex_row_even_distribution() {
let flex = FlexLayout::row();
let children = vec![
DesiredSize::new(50.0, 30.0),
DesiredSize::new(50.0, 30.0),
DesiredSize::new(50.0, 30.0),
];
let area = Rect::new(0.0, 0.0, 300.0, 100.0);
let rects = flex.arrange(&children, area);
assert_eq!(rects.len(), 3);
assert!((rects[0].x - 0.0).abs() < 0.01);
assert!((rects[1].x - 50.0).abs() < 0.01);
assert!((rects[2].x - 100.0).abs() < 0.01);
}
#[test]
fn flex_column_stacks_vertically() {
let flex = FlexLayout::column();
let children = vec![DesiredSize::new(100.0, 40.0), DesiredSize::new(100.0, 60.0)];
let area = Rect::new(0.0, 0.0, 200.0, 200.0);
let rects = flex.arrange(&children, area);
assert_eq!(rects.len(), 2);
assert!((rects[0].y - 0.0).abs() < 0.01);
assert!((rects[1].y - 40.0).abs() < 0.01);
}
#[test]
fn flex_spacing() {
let flex = FlexLayout::row().spacing(10.0);
let children = vec![DesiredSize::new(50.0, 30.0), DesiredSize::new(50.0, 30.0)];
let area = Rect::new(0.0, 0.0, 200.0, 100.0);
let rects = flex.arrange(&children, area);
assert!((rects[1].x - 60.0).abs() < 0.01); }
#[test]
fn flex_measure_row() {
let flex = FlexLayout::row().spacing(5.0);
let children = vec![DesiredSize::new(40.0, 20.0), DesiredSize::new(60.0, 30.0)];
let size = flex.measure(&children, SizeConstraints::unconstrained());
assert!((size.width - 105.0).abs() < 0.01); assert!((size.height - 30.0).abs() < 0.01); }
#[test]
fn flex_wrap_wraps_to_next_line() {
let flex = FlexLayout::row().wrap(FlexWrap::Wrap);
let children = vec![
DesiredSize::new(60.0, 30.0),
DesiredSize::new(60.0, 30.0),
DesiredSize::new(60.0, 30.0),
];
let area = Rect::new(0.0, 0.0, 130.0, 200.0);
let rects = flex.arrange(&children, area);
assert!((rects[2].y - 30.0).abs() < 0.01);
}
#[test]
fn grid_basic_layout() {
let grid = GridLayout::new(2, 2);
let children = vec![
DesiredSize::new(50.0, 50.0),
DesiredSize::new(50.0, 50.0),
DesiredSize::new(50.0, 50.0),
DesiredSize::new(50.0, 50.0),
];
let area = Rect::new(0.0, 0.0, 200.0, 200.0);
let rects = grid.arrange(&children, area);
assert_eq!(rects.len(), 4);
assert!((rects[0].width - 100.0).abs() < 0.01);
assert!((rects[0].height - 100.0).abs() < 0.01);
}
#[test]
fn grid_measure() {
let grid = GridLayout::new(3, 2).gap(4.0);
let children = vec![
DesiredSize::new(30.0, 20.0),
DesiredSize::new(30.0, 20.0),
DesiredSize::new(30.0, 20.0),
DesiredSize::new(30.0, 20.0),
DesiredSize::new(30.0, 20.0),
DesiredSize::new(30.0, 20.0),
];
let size = grid.measure(&children, SizeConstraints::unconstrained());
assert!((size.width - 98.0).abs() < 0.01); assert!((size.height - 44.0).abs() < 0.01); }
#[test]
fn grid_with_spans() {
let grid = GridLayout::new(3, 2)
.item(GridItem::new(0).col_span(2))
.item(GridItem::new(1));
let children = vec![DesiredSize::new(50.0, 30.0), DesiredSize::new(50.0, 30.0)];
let area = Rect::new(0.0, 0.0, 300.0, 200.0);
let rects = grid.arrange(&children, area);
assert!((rects[0].width - 200.0).abs() < 0.01);
}
#[test]
fn constraint_layout_min_max() {
let layout =
ConstraintLayout::new().rule(ConstraintRule::new(0).width(MinMax::new(50.0, 200.0)));
let children = vec![DesiredSize::new(300.0, 40.0)];
let area = Rect::new(0.0, 0.0, 400.0, 400.0);
let rects = layout.arrange(&children, area);
assert!((rects[0].width - 200.0).abs() < 0.01); }
#[test]
fn constraint_layout_unconstrained_children() {
let layout = ConstraintLayout::new();
let children = vec![DesiredSize::new(80.0, 60.0)];
let area = Rect::new(10.0, 20.0, 400.0, 300.0);
let rects = layout.arrange(&children, area);
assert!((rects[0].x - 10.0).abs() < 0.01);
assert!((rects[0].y - 20.0).abs() < 0.01);
assert!((rects[0].width - 80.0).abs() < 0.01);
}
#[test]
fn scroll_container_clips_content() {
let scroll = ScrollContainer::new(ScrollDirection::Vertical);
let children = vec![DesiredSize::new(100.0, 500.0)];
let area = Rect::new(0.0, 0.0, 100.0, 200.0);
let rects = scroll.arrange(&children, area);
assert!((rects[0].height - 500.0).abs() < 0.01); }
#[test]
fn scroll_with_offset() {
let scroll = ScrollContainer::new(ScrollDirection::Vertical).offset(50.0);
let children = vec![DesiredSize::new(100.0, 500.0)];
let area = Rect::new(0.0, 0.0, 100.0, 200.0);
let rects = scroll.arrange(&children, area);
assert!((rects[0].y - (-50.0)).abs() < 0.01); }
#[test]
fn scroll_measure_passes_through() {
let scroll = ScrollContainer::new(ScrollDirection::Both);
let children = vec![DesiredSize::new(800.0, 600.0)];
let size = scroll.measure(&children, SizeConstraints::unconstrained());
assert!((size.width - 800.0).abs() < 0.01);
assert!((size.height - 600.0).abs() < 0.01);
}
#[test]
fn responsive_selects_correct_breakpoint() {
let layout = ResponsiveLayout::new()
.breakpoint(Breakpoint::new(0.0, FlexLayout::column()))
.breakpoint(Breakpoint::new(600.0, FlexLayout::row()));
let children = vec![DesiredSize::new(50.0, 30.0), DesiredSize::new(50.0, 30.0)];
let wide = Rect::new(0.0, 0.0, 800.0, 400.0);
let rects = layout.arrange(&children, wide);
assert!((rects[1].x - 50.0).abs() < 0.01);
let narrow = Rect::new(0.0, 0.0, 400.0, 400.0);
let rects = layout.arrange(&children, narrow);
assert!((rects[1].y - 30.0).abs() < 0.01); }
#[test]
fn responsive_measure_uses_widest() {
let layout = ResponsiveLayout::new().breakpoint(Breakpoint::new(0.0, FlexLayout::column()));
let children = vec![DesiredSize::new(50.0, 30.0)];
let size = layout.measure(&children, SizeConstraints::unconstrained());
assert!((size.width - 50.0).abs() < 0.01);
}
}