use std::{cell::RefCell, collections::HashMap, iter, num::NonZeroUsize, rc::Rc, sync::OnceLock};
use cassowary::{
strength::REQUIRED,
AddConstraintError, Expression, Solver, Variable,
WeightedRelation::{EQ, GE, LE},
};
use itertools::Itertools;
use lru::LruCache;
use self::strengths::{
ALL_SEGMENT_GROW, FILL_GROW, GROW, LENGTH_SIZE_EQ, MAX_SIZE_EQ, MAX_SIZE_LE, MIN_SIZE_EQ,
MIN_SIZE_GE, PERCENTAGE_SIZE_EQ, RATIO_SIZE_EQ, SPACER_SIZE_EQ, SPACE_GROW,
};
use super::Flex;
use crate::prelude::*;
type Rects = Rc<[Rect]>;
type Segments = Rects;
type Spacers = Rects;
type Cache = LruCache<(Rect, Layout), (Segments, Spacers)>;
const FLOAT_PRECISION_MULTIPLIER: f64 = 100.0;
thread_local! {
static LAYOUT_CACHE: OnceLock<RefCell<Cache>> = const { OnceLock::new() };
}
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
pub struct Layout {
direction: Direction,
constraints: Vec<Constraint>,
margin: Margin,
flex: Flex,
spacing: u16,
}
impl Layout {
pub const DEFAULT_CACHE_SIZE: usize = 500;
pub fn new<I>(direction: Direction, constraints: I) -> Self
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
Self {
direction,
constraints: constraints.into_iter().map(Into::into).collect(),
..Self::default()
}
}
pub fn vertical<I>(constraints: I) -> Self
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
Self::new(Direction::Vertical, constraints.into_iter().map(Into::into))
}
pub fn horizontal<I>(constraints: I) -> Self
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
Self::new(
Direction::Horizontal,
constraints.into_iter().map(Into::into),
)
}
pub fn init_cache(cache_size: usize) -> bool {
LAYOUT_CACHE
.with(|c| {
c.set(RefCell::new(LruCache::new(
NonZeroUsize::new(cache_size).unwrap(),
)))
})
.is_ok()
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn direction(mut self, direction: Direction) -> Self {
self.direction = direction;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub fn constraints<I>(mut self, constraints: I) -> Self
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
self.constraints = constraints.into_iter().map(Into::into).collect();
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn margin(mut self, margin: u16) -> Self {
self.margin = Margin {
horizontal: margin,
vertical: margin,
};
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn horizontal_margin(mut self, horizontal: u16) -> Self {
self.margin.horizontal = horizontal;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn vertical_margin(mut self, vertical: u16) -> Self {
self.margin.vertical = vertical;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn flex(mut self, flex: Flex) -> Self {
self.flex = flex;
self
}
#[must_use = "method moves the value of self and returns the modified value"]
pub const fn spacing(mut self, spacing: u16) -> Self {
self.spacing = spacing;
self
}
pub fn areas<const N: usize>(&self, area: Rect) -> [Rect; N] {
let (areas, _) = self.split_with_spacers(area);
areas.to_vec().try_into().expect("invalid number of rects")
}
pub fn spacers<const N: usize>(&self, area: Rect) -> [Rect; N] {
let (_, spacers) = self.split_with_spacers(area);
spacers
.to_vec()
.try_into()
.expect("invalid number of rects")
}
pub fn split(&self, area: Rect) -> Rects {
self.split_with_spacers(area).0
}
pub fn split_with_spacers(&self, area: Rect) -> (Segments, Spacers) {
LAYOUT_CACHE.with(|c| {
c.get_or_init(|| {
RefCell::new(LruCache::new(
NonZeroUsize::new(Self::DEFAULT_CACHE_SIZE).unwrap(),
))
})
.borrow_mut()
.get_or_insert((area, self.clone()), || {
self.try_split(area).expect("failed to split")
})
.clone()
})
}
fn try_split(&self, area: Rect) -> Result<(Segments, Spacers), AddConstraintError> {
let mut solver = Solver::new();
let inner_area = area.inner(&self.margin);
let (area_start, area_end) = match self.direction {
Direction::Horizontal => (
f64::from(inner_area.x) * FLOAT_PRECISION_MULTIPLIER,
f64::from(inner_area.right()) * FLOAT_PRECISION_MULTIPLIER,
),
Direction::Vertical => (
f64::from(inner_area.y) * FLOAT_PRECISION_MULTIPLIER,
f64::from(inner_area.bottom()) * FLOAT_PRECISION_MULTIPLIER,
),
};
let variable_count = self.constraints.len() * 2 + 2;
let variables = iter::repeat_with(Variable::new)
.take(variable_count)
.collect_vec();
let spacers = variables
.iter()
.tuples()
.map(|(a, b)| Element::from((*a, *b)))
.collect_vec();
let segments = variables
.iter()
.skip(1)
.tuples()
.map(|(a, b)| Element::from((*a, *b)))
.collect_vec();
let flex = self.flex;
let spacing = self.spacing;
let constraints = &self.constraints;
let area_size = Element::from((*variables.first().unwrap(), *variables.last().unwrap()));
configure_area(&mut solver, area_size, area_start, area_end)?;
configure_variable_constraints(&mut solver, &variables, area_size)?;
configure_flex_constraints(&mut solver, area_size, &spacers, flex, spacing)?;
configure_constraints(&mut solver, area_size, &segments, constraints, flex)?;
configure_fill_constraints(&mut solver, &segments, constraints, flex)?;
if !flex.is_legacy() {
for (left, right) in segments.iter().tuple_windows() {
solver.add_constraint(left.has_size(right, ALL_SEGMENT_GROW))?;
}
}
let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
let segment_rects = changes_to_rects(&changes, &segments, inner_area, self.direction);
let spacer_rects = changes_to_rects(&changes, &spacers, inner_area, self.direction);
Ok((segment_rects, spacer_rects))
}
}
fn configure_area(
solver: &mut Solver,
area: Element,
area_start: f64,
area_end: f64,
) -> Result<(), AddConstraintError> {
solver.add_constraint(area.start | EQ(REQUIRED) | area_start)?;
solver.add_constraint(area.end | EQ(REQUIRED) | area_end)?;
Ok(())
}
fn configure_variable_constraints(
solver: &mut Solver,
variables: &[Variable],
area: Element,
) -> Result<(), AddConstraintError> {
for &variable in variables {
solver.add_constraint(variable | GE(REQUIRED) | area.start)?;
solver.add_constraint(variable | LE(REQUIRED) | area.end)?;
}
for (&left, &right) in variables.iter().tuple_windows() {
solver.add_constraint(left | LE(REQUIRED) | right)?;
}
Ok(())
}
fn configure_constraints(
solver: &mut Solver,
area: Element,
segments: &[Element],
constraints: &[Constraint],
flex: Flex,
) -> Result<(), AddConstraintError> {
for (&constraint, &element) in constraints.iter().zip(segments.iter()) {
match constraint {
Constraint::Max(max) => {
solver.add_constraint(element.has_max_size(max, MAX_SIZE_LE))?;
solver.add_constraint(element.has_int_size(max, MAX_SIZE_EQ))?;
}
Constraint::Min(min) => {
solver.add_constraint(element.has_min_size(min, MIN_SIZE_GE))?;
if flex.is_legacy() {
solver.add_constraint(element.has_int_size(min, MIN_SIZE_EQ))?;
} else {
solver.add_constraint(element.has_size(area, FILL_GROW))?;
}
}
Constraint::Length(length) => {
solver.add_constraint(element.has_int_size(length, LENGTH_SIZE_EQ))?;
}
Constraint::Percentage(p) => {
let size = area.size() * f64::from(p) / 100.00;
solver.add_constraint(element.has_size(size, PERCENTAGE_SIZE_EQ))?;
}
Constraint::Ratio(num, den) => {
let size = area.size() * f64::from(num) / f64::from(den.max(1));
solver.add_constraint(element.has_size(size, RATIO_SIZE_EQ))?;
}
Constraint::Fill(_) => {
solver.add_constraint(element.has_size(area, FILL_GROW))?;
}
}
}
Ok(())
}
fn configure_flex_constraints(
solver: &mut Solver,
area: Element,
spacers: &[Element],
flex: Flex,
spacing: u16,
) -> Result<(), AddConstraintError> {
let spacers_except_first_and_last = spacers.get(1..spacers.len() - 1).unwrap_or(&[]);
let spacing_f64 = f64::from(spacing) * FLOAT_PRECISION_MULTIPLIER;
match flex {
Flex::Legacy => {
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
}
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
solver.add_constraint(first.is_empty())?;
solver.add_constraint(last.is_empty())?;
}
}
Flex::SpaceAround => {
for (left, right) in spacers.iter().tuple_combinations() {
solver.add_constraint(left.has_size(right, SPACER_SIZE_EQ))?;
}
for spacer in spacers {
solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
}
}
Flex::SpaceBetween => {
for (left, right) in spacers_except_first_and_last.iter().tuple_combinations() {
solver.add_constraint(left.has_size(right.size(), SPACER_SIZE_EQ))?;
}
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_min_size(spacing, SPACER_SIZE_EQ))?;
}
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_size(area, SPACE_GROW))?;
}
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
solver.add_constraint(first.is_empty())?;
solver.add_constraint(last.is_empty())?;
}
}
Flex::Start => {
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
}
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
solver.add_constraint(first.is_empty())?;
solver.add_constraint(last.has_size(area, GROW))?;
}
}
Flex::Center => {
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
}
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
solver.add_constraint(first.has_size(area, GROW))?;
solver.add_constraint(last.has_size(area, GROW))?;
solver.add_constraint(first.has_size(last, SPACER_SIZE_EQ))?;
}
}
Flex::End => {
for spacer in spacers_except_first_and_last {
solver.add_constraint(spacer.has_size(spacing_f64, SPACER_SIZE_EQ))?;
}
if let (Some(first), Some(last)) = (spacers.first(), spacers.last()) {
solver.add_constraint(last.is_empty())?;
solver.add_constraint(first.has_size(area, GROW))?;
}
}
}
Ok(())
}
fn configure_fill_constraints(
solver: &mut Solver,
segments: &[Element],
constraints: &[Constraint],
flex: Flex,
) -> Result<(), AddConstraintError> {
for ((&left_constraint, &left_element), (&right_constraint, &right_element)) in constraints
.iter()
.zip(segments.iter())
.filter(|(c, _)| c.is_fill() || (!flex.is_legacy() && c.is_min()))
.tuple_combinations()
{
let left_scaling_factor = match left_constraint {
Constraint::Fill(scale) => f64::from(scale).max(1e-6),
Constraint::Min(_) => 1.0,
_ => unreachable!(),
};
let right_scaling_factor = match right_constraint {
Constraint::Fill(scale) => f64::from(scale).max(1e-6),
Constraint::Min(_) => 1.0,
_ => unreachable!(),
};
solver.add_constraint(
(right_scaling_factor * left_element.size())
| EQ(GROW)
| (left_scaling_factor * right_element.size()),
)?;
}
Ok(())
}
fn changes_to_rects(
changes: &HashMap<Variable, f64>,
elements: &[Element],
area: Rect,
direction: Direction,
) -> Rects {
elements
.iter()
.map(|element| {
let start = changes.get(&element.start).unwrap_or(&0.0);
let end = changes.get(&element.end).unwrap_or(&0.0);
let start = (start.round() / FLOAT_PRECISION_MULTIPLIER).round() as u16;
let end = (end.round() / FLOAT_PRECISION_MULTIPLIER).round() as u16;
let size = end.saturating_sub(start);
match direction {
Direction::Horizontal => Rect {
x: start,
y: area.y,
width: size,
height: area.height,
},
Direction::Vertical => Rect {
x: area.x,
y: start,
width: area.width,
height: size,
},
}
})
.collect::<Rects>()
}
#[allow(dead_code)]
fn debug_segments(segments: &[Element], changes: &HashMap<Variable, f64>) {
let ends = format!(
"{:?}",
segments
.iter()
.map(|e| changes.get(&e.end).unwrap_or(&0.0))
.collect::<Vec<&f64>>()
);
dbg!(ends);
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
struct Element {
start: Variable,
end: Variable,
}
impl From<(Variable, Variable)> for Element {
fn from((start, end): (Variable, Variable)) -> Self {
Self { start, end }
}
}
impl Element {
#[allow(dead_code)]
fn new() -> Self {
Self {
start: Variable::new(),
end: Variable::new(),
}
}
fn size(&self) -> Expression {
self.end - self.start
}
fn has_max_size(&self, size: u16, strength: f64) -> cassowary::Constraint {
self.size() | LE(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
}
fn has_min_size(&self, size: u16, strength: f64) -> cassowary::Constraint {
self.size() | GE(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
}
fn has_int_size(&self, size: u16, strength: f64) -> cassowary::Constraint {
self.size() | EQ(strength) | (f64::from(size) * FLOAT_PRECISION_MULTIPLIER)
}
fn has_size<E: Into<Expression>>(&self, size: E, strength: f64) -> cassowary::Constraint {
self.size() | EQ(strength) | size.into()
}
fn is_empty(&self) -> cassowary::Constraint {
self.size() | EQ(REQUIRED - 1.0) | 0.0
}
}
impl From<Element> for Expression {
fn from(element: Element) -> Self {
element.size()
}
}
impl From<&Element> for Expression {
fn from(element: &Element) -> Self {
element.size()
}
}
mod strengths {
use cassowary::strength::{MEDIUM, REQUIRED, STRONG, WEAK};
pub const SPACER_SIZE_EQ: f64 = REQUIRED - 1.0;
pub const MIN_SIZE_GE: f64 = STRONG * 100.0;
pub const MAX_SIZE_LE: f64 = STRONG * 100.0;
pub const LENGTH_SIZE_EQ: f64 = STRONG * 10.0;
pub const PERCENTAGE_SIZE_EQ: f64 = STRONG;
pub const RATIO_SIZE_EQ: f64 = STRONG / 10.0;
pub const MIN_SIZE_EQ: f64 = MEDIUM * 10.0;
pub const MAX_SIZE_EQ: f64 = MEDIUM * 10.0;
pub const FILL_GROW: f64 = MEDIUM;
pub const GROW: f64 = MEDIUM / 10.0;
pub const SPACE_GROW: f64 = WEAK * 10.0;
pub const ALL_SEGMENT_GROW: f64 = WEAK;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::assertions_on_constants)]
pub fn strength_is_valid() {
use strengths::*;
assert!(SPACER_SIZE_EQ > MAX_SIZE_LE);
assert!(MAX_SIZE_LE > MAX_SIZE_EQ);
assert!(MIN_SIZE_GE == MAX_SIZE_LE);
assert!(MAX_SIZE_LE > LENGTH_SIZE_EQ);
assert!(LENGTH_SIZE_EQ > PERCENTAGE_SIZE_EQ);
assert!(PERCENTAGE_SIZE_EQ > RATIO_SIZE_EQ);
assert!(RATIO_SIZE_EQ > MAX_SIZE_EQ);
assert!(MIN_SIZE_GE > FILL_GROW);
assert!(FILL_GROW > GROW);
assert!(GROW > SPACE_GROW);
assert!(SPACE_GROW > ALL_SEGMENT_GROW);
}
#[test]
fn custom_cache_size() {
assert!(Layout::init_cache(10));
assert!(!Layout::init_cache(15));
LAYOUT_CACHE.with(|c| {
assert_eq!(c.get().unwrap().borrow().cap().get(), 10);
});
}
#[test]
fn default_cache_size() {
let target = Rect {
x: 2,
y: 2,
width: 10,
height: 10,
};
Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(10),
Constraint::Max(5),
Constraint::Min(1),
])
.split(target);
assert!(!Layout::init_cache(15));
LAYOUT_CACHE.with(|c| {
assert_eq!(
c.get().unwrap().borrow().cap().get(),
Layout::DEFAULT_CACHE_SIZE
);
});
}
#[test]
fn default() {
assert_eq!(
Layout::default(),
Layout {
direction: Direction::Vertical,
margin: Margin::new(0, 0),
constraints: vec![],
flex: Flex::default(),
spacing: 0,
}
);
}
#[test]
fn new() {
let fixed_size_array = [Constraint::Min(0)];
let layout = Layout::new(Direction::Horizontal, fixed_size_array);
assert_eq!(layout.direction, Direction::Horizontal);
assert_eq!(layout.constraints, [Constraint::Min(0)]);
#[allow(clippy::needless_borrows_for_generic_args)] let layout = Layout::new(Direction::Horizontal, &[Constraint::Min(0)]);
assert_eq!(layout.direction, Direction::Horizontal);
assert_eq!(layout.constraints, [Constraint::Min(0)]);
let layout = Layout::new(Direction::Horizontal, vec![Constraint::Min(0)]);
assert_eq!(layout.direction, Direction::Horizontal);
assert_eq!(layout.constraints, [Constraint::Min(0)]);
#[allow(clippy::needless_borrows_for_generic_args)] let layout = Layout::new(Direction::Horizontal, &(vec![Constraint::Min(0)]));
assert_eq!(layout.direction, Direction::Horizontal);
assert_eq!(layout.constraints, [Constraint::Min(0)]);
let layout = Layout::new(Direction::Horizontal, iter::once(Constraint::Min(0)));
assert_eq!(layout.direction, Direction::Horizontal);
assert_eq!(layout.constraints, [Constraint::Min(0)]);
}
#[test]
fn vertical() {
assert_eq!(
Layout::vertical([Constraint::Min(0)]),
Layout {
direction: Direction::Vertical,
margin: Margin::new(0, 0),
constraints: vec![Constraint::Min(0)],
flex: Flex::default(),
spacing: 0,
}
);
}
#[test]
fn horizontal() {
assert_eq!(
Layout::horizontal([Constraint::Min(0)]),
Layout {
direction: Direction::Horizontal,
margin: Margin::new(0, 0),
constraints: vec![Constraint::Min(0)],
flex: Flex::default(),
spacing: 0,
}
);
}
#[test]
#[allow(
clippy::needless_borrow,
clippy::unnecessary_to_owned,
clippy::useless_asref
)]
fn constraints() {
const CONSTRAINTS: [Constraint; 2] = [Constraint::Min(0), Constraint::Max(10)];
let fixed_size_array = CONSTRAINTS;
assert_eq!(
Layout::default().constraints(fixed_size_array).constraints,
CONSTRAINTS,
"constraints should be settable with an array"
);
let slice_of_fixed_size_array = &CONSTRAINTS;
assert_eq!(
Layout::default()
.constraints(slice_of_fixed_size_array)
.constraints,
CONSTRAINTS,
"constraints should be settable with a slice"
);
let vec = CONSTRAINTS.to_vec();
let slice_of_vec = vec.as_slice();
assert_eq!(
Layout::default().constraints(slice_of_vec).constraints,
CONSTRAINTS,
"constraints should be settable with a slice"
);
assert_eq!(
Layout::default().constraints(vec).constraints,
CONSTRAINTS,
"constraints should be settable with a Vec"
);
let iter = CONSTRAINTS.iter();
assert_eq!(
Layout::default().constraints(iter).constraints,
CONSTRAINTS,
"constraints should be settable with an iter"
);
let iterator = CONSTRAINTS.iter().map(ToOwned::to_owned);
assert_eq!(
Layout::default().constraints(iterator).constraints,
CONSTRAINTS,
"constraints should be settable with an iterator"
);
let iterator_ref = CONSTRAINTS.iter().map(AsRef::as_ref);
assert_eq!(
Layout::default().constraints(iterator_ref).constraints,
CONSTRAINTS,
"constraints should be settable with an iterator of refs"
);
}
#[test]
fn direction() {
assert_eq!(
Layout::default().direction(Direction::Horizontal).direction,
Direction::Horizontal
);
assert_eq!(
Layout::default().direction(Direction::Vertical).direction,
Direction::Vertical
);
}
#[test]
fn margins() {
assert_eq!(Layout::default().margin(10).margin, Margin::new(10, 10));
assert_eq!(
Layout::default().horizontal_margin(10).margin,
Margin::new(10, 0)
);
assert_eq!(
Layout::default().vertical_margin(10).margin,
Margin::new(0, 10)
);
assert_eq!(
Layout::default()
.horizontal_margin(10)
.vertical_margin(20)
.margin,
Margin::new(10, 20)
);
}
#[test]
fn flex() {
assert_eq!(Layout::default().flex, Flex::Start);
assert_eq!(Layout::default().flex(Flex::Center).flex, Flex::Center);
}
#[test]
fn spacing() {
assert_eq!(Layout::default().spacing(10).spacing, 10);
assert_eq!(Layout::default().spacing(0).spacing, 0);
}
mod split {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::{
assert_buffer_eq,
layout::flex::Flex,
prelude::{Constraint::*, *},
widgets::Paragraph,
};
#[track_caller]
fn letters(flex: Flex, constraints: &[Constraint], width: u16, expected: &str) {
let area = Rect::new(0, 0, width, 1);
let layout = Layout::default()
.direction(Direction::Horizontal)
.constraints(constraints)
.flex(flex)
.split(area);
let mut buffer = Buffer::empty(area);
for (i, c) in ('a'..='z').take(constraints.len()).enumerate() {
let s = c.to_string().repeat(area.width as usize);
Paragraph::new(s).render(layout[i], &mut buffer);
}
let expected = Buffer::with_lines(vec![expected]);
assert_buffer_eq!(buffer, expected);
}
#[rstest]
#[case(Flex::Legacy, 1, &[Length(0)], "a")] #[case(Flex::Legacy, 1, &[Length(1)], "a")] #[case(Flex::Legacy, 1, &[Length(2)], "a")] #[case(Flex::Legacy, 2, &[Length(0)], "aa")] #[case(Flex::Legacy, 2, &[Length(1)], "aa")] #[case(Flex::Legacy, 2, &[Length(2)], "aa")] #[case(Flex::Legacy, 2, &[Length(3)], "aa")] #[case(Flex::Legacy, 1, &[Length(0), Length(0)], "b")] #[case(Flex::Legacy, 1, &[Length(0), Length(1)], "b")] #[case(Flex::Legacy, 1, &[Length(0), Length(2)], "b")] #[case(Flex::Legacy, 1, &[Length(1), Length(0)], "a")] #[case(Flex::Legacy, 1, &[Length(1), Length(1)], "a")] #[case(Flex::Legacy, 1, &[Length(1), Length(2)], "a")] #[case(Flex::Legacy, 1, &[Length(2), Length(0)], "a")] #[case(Flex::Legacy, 1, &[Length(2), Length(1)], "a")] #[case(Flex::Legacy, 1, &[Length(2), Length(2)], "a")] #[case(Flex::Legacy, 2, &[Length(0), Length(0)], "bb")] #[case(Flex::Legacy, 2, &[Length(0), Length(1)], "bb")] #[case(Flex::Legacy, 2, &[Length(0), Length(2)], "bb")] #[case(Flex::Legacy, 2, &[Length(0), Length(3)], "bb")] #[case(Flex::Legacy, 2, &[Length(1), Length(0)], "ab")] #[case(Flex::Legacy, 2, &[Length(1), Length(1)], "ab")] #[case(Flex::Legacy, 2, &[Length(1), Length(2)], "ab")] #[case(Flex::Legacy, 2, &[Length(1), Length(3)], "ab")] #[case(Flex::Legacy, 2, &[Length(2), Length(0)], "aa")] #[case(Flex::Legacy, 2, &[Length(2), Length(1)], "aa")] #[case(Flex::Legacy, 2, &[Length(2), Length(2)], "aa")] #[case(Flex::Legacy, 2, &[Length(2), Length(3)], "aa")] #[case(Flex::Legacy, 2, &[Length(3), Length(0)], "aa")] #[case(Flex::Legacy, 2, &[Length(3), Length(1)], "aa")] #[case(Flex::Legacy, 2, &[Length(3), Length(2)], "aa")] #[case(Flex::Legacy, 2, &[Length(3), Length(3)], "aa")] #[case(Flex::Legacy, 3, &[Length(2), Length(2)], "aab")] fn length(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::Legacy, 1, &[Max(0)], "a")] #[case(Flex::Legacy, 1, &[Max(1)], "a")] #[case(Flex::Legacy, 1, &[Max(2)], "a")] #[case(Flex::Legacy, 2, &[Max(0)], "aa")] #[case(Flex::Legacy, 2, &[Max(1)], "aa")] #[case(Flex::Legacy, 2, &[Max(2)], "aa")] #[case(Flex::Legacy, 2, &[Max(3)], "aa")] #[case(Flex::Legacy, 1, &[Max(0), Max(0)], "b")] #[case(Flex::Legacy, 1, &[Max(0), Max(1)], "b")] #[case(Flex::Legacy, 1, &[Max(0), Max(2)], "b")] #[case(Flex::Legacy, 1, &[Max(1), Max(0)], "a")] #[case(Flex::Legacy, 1, &[Max(1), Max(1)], "a")] #[case(Flex::Legacy, 1, &[Max(1), Max(2)], "a")] #[case(Flex::Legacy, 1, &[Max(2), Max(0)], "a")] #[case(Flex::Legacy, 1, &[Max(2), Max(1)], "a")] #[case(Flex::Legacy, 1, &[Max(2), Max(2)], "a")] #[case(Flex::Legacy, 2, &[Max(0), Max(0)], "bb")] #[case(Flex::Legacy, 2, &[Max(0), Max(1)], "bb")] #[case(Flex::Legacy, 2, &[Max(0), Max(2)], "bb")] #[case(Flex::Legacy, 2, &[Max(0), Max(3)], "bb")] #[case(Flex::Legacy, 2, &[Max(1), Max(0)], "ab")] #[case(Flex::Legacy, 2, &[Max(1), Max(1)], "ab")] #[case(Flex::Legacy, 2, &[Max(1), Max(2)], "ab")] #[case(Flex::Legacy, 2, &[Max(1), Max(3)], "ab")] #[case(Flex::Legacy, 2, &[Max(2), Max(0)], "aa")] #[case(Flex::Legacy, 2, &[Max(2), Max(1)], "aa")] #[case(Flex::Legacy, 2, &[Max(2), Max(2)], "aa")] #[case(Flex::Legacy, 2, &[Max(2), Max(3)], "aa")] #[case(Flex::Legacy, 2, &[Max(3), Max(0)], "aa")] #[case(Flex::Legacy, 2, &[Max(3), Max(1)], "aa")] #[case(Flex::Legacy, 2, &[Max(3), Max(2)], "aa")] #[case(Flex::Legacy, 2, &[Max(3), Max(3)], "aa")] #[case(Flex::Legacy, 3, &[Max(2), Max(2)], "aab")]
fn max(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::Legacy, 1, &[Min(0), Min(0)], "b")] #[case(Flex::Legacy, 1, &[Min(0), Min(1)], "b")] #[case(Flex::Legacy, 1, &[Min(0), Min(2)], "b")] #[case(Flex::Legacy, 1, &[Min(1), Min(0)], "a")] #[case(Flex::Legacy, 1, &[Min(1), Min(1)], "a")] #[case(Flex::Legacy, 1, &[Min(1), Min(2)], "a")] #[case(Flex::Legacy, 1, &[Min(2), Min(0)], "a")] #[case(Flex::Legacy, 1, &[Min(2), Min(1)], "a")] #[case(Flex::Legacy, 1, &[Min(2), Min(2)], "a")] #[case(Flex::Legacy, 2, &[Min(0), Min(0)], "bb")] #[case(Flex::Legacy, 2, &[Min(0), Min(1)], "bb")] #[case(Flex::Legacy, 2, &[Min(0), Min(2)], "bb")] #[case(Flex::Legacy, 2, &[Min(0), Min(3)], "bb")] #[case(Flex::Legacy, 2, &[Min(1), Min(0)], "ab")] #[case(Flex::Legacy, 2, &[Min(1), Min(1)], "ab")] #[case(Flex::Legacy, 2, &[Min(1), Min(2)], "ab")] #[case(Flex::Legacy, 2, &[Min(1), Min(3)], "ab")] #[case(Flex::Legacy, 2, &[Min(2), Min(0)], "aa")] #[case(Flex::Legacy, 2, &[Min(2), Min(1)], "aa")] #[case(Flex::Legacy, 2, &[Min(2), Min(2)], "aa")] #[case(Flex::Legacy, 2, &[Min(2), Min(3)], "aa")] #[case(Flex::Legacy, 2, &[Min(3), Min(0)], "aa")] #[case(Flex::Legacy, 2, &[Min(3), Min(1)], "aa")] #[case(Flex::Legacy, 2, &[Min(3), Min(2)], "aa")] #[case(Flex::Legacy, 2, &[Min(3), Min(3)], "aa")] #[case(Flex::Legacy, 3, &[Min(2), Min(2)], "aab")]
fn min(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest] #[case(Flex::Legacy, 1, &[Percentage(0)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(25)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(50)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(90)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(100)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(200)], "a")]
#[case(Flex::Legacy, 2, &[Percentage(0)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(10)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(25)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(50)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(66)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(100)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(200)], "aa")]
#[case(Flex::Legacy, 10, &[Percentage(0)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 10, &[Percentage(10)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 10, &[Percentage(25)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 10, &[Percentage(50)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 10, &[Percentage(66)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 10, &[Percentage(100)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 10, &[Percentage(200)], "aaaaaaaaaa")]
#[case(Flex::Legacy, 1, &[Percentage(0), Percentage(0)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(0), Percentage(10)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(0), Percentage(50)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(0), Percentage(90)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(0), Percentage(100)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(0), Percentage(200)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(10), Percentage(0)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(10), Percentage(10)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(10), Percentage(50)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(10), Percentage(90)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(10), Percentage(100)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(10), Percentage(200)], "b")]
#[case(Flex::Legacy, 1, &[Percentage(50), Percentage(0)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(50), Percentage(50)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(50), Percentage(100)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(50), Percentage(200)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(90), Percentage(0)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(90), Percentage(50)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(90), Percentage(100)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(90), Percentage(200)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(100), Percentage(0)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(100), Percentage(50)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(100), Percentage(100)], "a")]
#[case(Flex::Legacy, 1, &[Percentage(100), Percentage(200)], "a")]
#[case(Flex::Legacy, 2, &[Percentage(0), Percentage(0)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(0), Percentage(25)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(0), Percentage(50)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(0), Percentage(100)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(0), Percentage(200)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(10), Percentage(0)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(10), Percentage(25)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(10), Percentage(50)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(10), Percentage(100)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(10), Percentage(200)], "bb")]
#[case(Flex::Legacy, 2, &[Percentage(25), Percentage(0)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(25), Percentage(25)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(25), Percentage(50)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(25), Percentage(100)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(25), Percentage(200)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(33), Percentage(0)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(33), Percentage(25)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(33), Percentage(50)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(33), Percentage(100)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(33), Percentage(200)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(50), Percentage(0)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(50), Percentage(50)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(50), Percentage(100)], "ab")]
#[case(Flex::Legacy, 2, &[Percentage(100), Percentage(0)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(100), Percentage(50)], "aa")]
#[case(Flex::Legacy, 2, &[Percentage(100), Percentage(100)], "aa")]
#[case(Flex::Legacy, 3, &[Percentage(33), Percentage(33)], "abb")]
#[case(Flex::Legacy, 3, &[Percentage(33), Percentage(66)], "abb")]
#[case(Flex::Legacy, 4, &[Percentage(33), Percentage(33)], "abbb")]
#[case(Flex::Legacy, 4, &[Percentage(33), Percentage(66)], "abbb")]
#[case(Flex::Legacy, 10, &[Percentage(0), Percentage(0)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(0), Percentage(25)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(0), Percentage(50)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(0), Percentage(100)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(0), Percentage(200)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(10), Percentage(0)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(10), Percentage(25)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(10), Percentage(50)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(10), Percentage(100)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(10), Percentage(200)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(25), Percentage(0)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(25), Percentage(25)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(25), Percentage(50)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(25), Percentage(100)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(25), Percentage(200)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(33), Percentage(0)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(33), Percentage(25)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(33), Percentage(50)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(33), Percentage(100)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(33), Percentage(200)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(50), Percentage(0)], "aaaaabbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(50), Percentage(50)], "aaaaabbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(50), Percentage(100)], "aaaaabbbbb" )]
#[case(Flex::Legacy, 10, &[Percentage(100), Percentage(0)], "aaaaaaaaaa" )]
#[case(Flex::Legacy, 10, &[Percentage(100), Percentage(50)], "aaaaaaaaaa" )]
#[case(Flex::Legacy, 10, &[Percentage(100), Percentage(100)], "aaaaaaaaaa" )]
fn percentage(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::Start, 10, &[Percentage(0), Percentage(0)], " " )]
#[case(Flex::Start, 10, &[Percentage(0), Percentage(25)], "bbb " )]
#[case(Flex::Start, 10, &[Percentage(0), Percentage(50)], "bbbbb " )]
#[case(Flex::Start, 10, &[Percentage(0), Percentage(100)], "bbbbbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(0), Percentage(200)], "bbbbbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(10), Percentage(0)], "a " )]
#[case(Flex::Start, 10, &[Percentage(10), Percentage(25)], "abbb " )]
#[case(Flex::Start, 10, &[Percentage(10), Percentage(50)], "abbbbb " )]
#[case(Flex::Start, 10, &[Percentage(10), Percentage(100)], "abbbbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(10), Percentage(200)], "abbbbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(25), Percentage(0)], "aaa " )]
#[case(Flex::Start, 10, &[Percentage(25), Percentage(25)], "aaabb " )]
#[case(Flex::Start, 10, &[Percentage(25), Percentage(50)], "aaabbbbb " )]
#[case(Flex::Start, 10, &[Percentage(25), Percentage(100)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(25), Percentage(200)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(33), Percentage(0)], "aaa " )]
#[case(Flex::Start, 10, &[Percentage(33), Percentage(25)], "aaabbb " )]
#[case(Flex::Start, 10, &[Percentage(33), Percentage(50)], "aaabbbbb " )]
#[case(Flex::Start, 10, &[Percentage(33), Percentage(100)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(33), Percentage(200)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Percentage(50), Percentage(0)], "aaaaa " )]
#[case(Flex::Start, 10, &[Percentage(50), Percentage(50)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Percentage(50), Percentage(100)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Percentage(100), Percentage(0)], "aaaaaaaaaa" )]
#[case(Flex::Start, 10, &[Percentage(100), Percentage(50)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Percentage(100), Percentage(100)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Percentage(100), Percentage(200)], "aaaaabbbbb" )]
fn percentage_start(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::SpaceBetween, 10, &[Percentage(0), Percentage(0)], " " )]
#[case(Flex::SpaceBetween, 10, &[Percentage(0), Percentage(25)], " bb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(0), Percentage(50)], " bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(0), Percentage(100)], "bbbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(0), Percentage(200)], "bbbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(10), Percentage(0)], "a " )]
#[case(Flex::SpaceBetween, 10, &[Percentage(10), Percentage(25)], "a bb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(10), Percentage(50)], "a bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(10), Percentage(100)], "abbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(10), Percentage(200)], "abbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(25), Percentage(0)], "aaa " )]
#[case(Flex::SpaceBetween, 10, &[Percentage(25), Percentage(25)], "aaa bb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(25), Percentage(50)], "aaa bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(25), Percentage(100)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(25), Percentage(200)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(33), Percentage(0)], "aaa " )]
#[case(Flex::SpaceBetween, 10, &[Percentage(33), Percentage(25)], "aaa bb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(33), Percentage(50)], "aaa bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(33), Percentage(100)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(33), Percentage(200)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(50), Percentage(0)], "aaaaa " )]
#[case(Flex::SpaceBetween, 10, &[Percentage(50), Percentage(50)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(50), Percentage(100)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(0)], "aaaaaaaaaa" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(50)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(100)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Percentage(100), Percentage(200)], "aaaaabbbbb" )]
fn percentage_spacebetween(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::Legacy, 1, &[Ratio(0, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 4)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 2)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(9, 10)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(2, 1)], "a")]
#[case(Flex::Legacy, 2, &[Ratio(0, 1)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(1, 10)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(1, 4)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(1, 2)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(2, 3)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(1, 1)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(2, 1)], "aa")]
#[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(0, 1)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(1, 10)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(1, 2)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(9, 10)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(1, 1)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(0, 1), Ratio(2, 1)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(0, 1)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(1, 10)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(1, 2)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(9, 10)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(1, 1)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 10), Ratio(2, 1)], "b")]
#[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(0, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(1, 2)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(1, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 2), Ratio(2, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(0, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(1, 2)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(1, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(9, 10), Ratio(2, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(0, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(1, 2)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(1, 1)], "a")]
#[case(Flex::Legacy, 1, &[Ratio(1, 1), Ratio(2, 1)], "a")]
#[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(0, 1)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(1, 4)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(1, 2)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(1, 1)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(0, 1), Ratio(2, 1)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(0, 1)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(1, 4)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(1, 2)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(1, 1)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(1, 10), Ratio(2, 1)], "bb")]
#[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(0, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(1, 4)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(1, 2)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(1, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 4), Ratio(2, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(0, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(1, 4)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(1, 2)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(1, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 3), Ratio(2, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 2), Ratio(0, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 2), Ratio(1, 2)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 2), Ratio(1, 1)], "ab")]
#[case(Flex::Legacy, 2, &[Ratio(1, 1), Ratio(0, 1)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(1, 1), Ratio(1, 2)], "aa")]
#[case(Flex::Legacy, 2, &[Ratio(1, 1), Ratio(1, 1)], "aa")]
#[case(Flex::Legacy, 3, &[Ratio(1, 3), Ratio(1, 3)], "abb")]
#[case(Flex::Legacy, 3, &[Ratio(1, 3), Ratio(2,3)], "abb")]
#[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(0, 1)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(1, 4)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(1, 2)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(1, 1)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(0, 1), Ratio(2, 1)], "bbbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(0, 1)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(1, 4)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(1, 2)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(1, 1)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 10), Ratio(2, 1)], "abbbbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(0, 1)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(1, 4)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(1, 2)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(1, 1)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 4), Ratio(2, 1)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(0, 1)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(1, 4)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(1, 2)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(1, 1)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 3), Ratio(2, 1)], "aaabbbbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 2), Ratio(0, 1)], "aaaaabbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 2), Ratio(1, 2)], "aaaaabbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 2), Ratio(1, 1)], "aaaaabbbbb" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 1), Ratio(0, 1)], "aaaaaaaaaa" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 1), Ratio(1, 2)], "aaaaaaaaaa" )]
#[case(Flex::Legacy, 10, &[Ratio(1, 1), Ratio(1, 1)], "aaaaaaaaaa" )]
fn ratio(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(0, 1)], " " )]
#[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(1, 4)], "bbb " )]
#[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(1, 2)], "bbbbb " )]
#[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(1, 1)], "bbbbbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(0, 1), Ratio(2, 1)], "bbbbbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(0, 1)], "a " )]
#[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(1, 4)], "abbb " )]
#[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(1, 2)], "abbbbb " )]
#[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(1, 1)], "abbbbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 10), Ratio(2, 1)], "abbbbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(0, 1)], "aaa " )]
#[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(1, 4)], "aaabb " )]
#[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(1, 2)], "aaabbbbb " )]
#[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(1, 1)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 4), Ratio(2, 1)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(0, 1)], "aaa " )]
#[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(1, 4)], "aaabbb " )]
#[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(1, 2)], "aaabbbbb " )]
#[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(1, 1)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 3), Ratio(2, 1)], "aaabbbbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 2), Ratio(0, 1)], "aaaaa " )]
#[case(Flex::Start, 10, &[Ratio(1, 2), Ratio(1, 2)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 2), Ratio(1, 1)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(0, 1)], "aaaaaaaaaa" )]
#[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(1, 2)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(1, 1)], "aaaaabbbbb" )]
#[case(Flex::Start, 10, &[Ratio(1, 1), Ratio(2, 1)], "aaaaabbbbb" )]
fn ratio_start(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[rstest]
#[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(0, 1)], " " )]
#[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(1, 4)], " bb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(1, 2)], " bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(1, 1)], "bbbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(0, 1), Ratio(2, 1)], "bbbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(0, 1)], "a " )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(1, 4)], "a bb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(1, 2)], "a bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(1, 1)], "abbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 10), Ratio(2, 1)], "abbbbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(0, 1)], "aaa " )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(1, 4)], "aaa bb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(1, 2)], "aaa bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(1, 1)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 4), Ratio(2, 1)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(0, 1)], "aaa " )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(1, 4)], "aaa bb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(1, 2)], "aaa bbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(1, 1)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 3), Ratio(2, 1)], "aaabbbbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 2), Ratio(0, 1)], "aaaaa " )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 2), Ratio(1, 2)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 2), Ratio(1, 1)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(0, 1)], "aaaaaaaaaa" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(1, 2)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(1, 1)], "aaaaabbbbb" )]
#[case(Flex::SpaceBetween, 10, &[Ratio(1, 1), Ratio(2, 1)], "aaaaabbbbb" )]
fn ratio_spacebetween(
#[case] flex: Flex,
#[case] width: u16,
#[case] constraints: &[Constraint],
#[case] expected: &str,
) {
letters(flex, constraints, width, expected);
}
#[test]
fn vertical_split_by_height() {
let target = Rect {
x: 2,
y: 2,
width: 10,
height: 10,
};
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints(
[
Constraint::Percentage(10),
Constraint::Max(5),
Constraint::Min(1),
]
.as_ref(),
)
.split(target);
assert_eq!(target.height, chunks.iter().map(|r| r.height).sum::<u16>());
chunks.windows(2).for_each(|w| assert!(w[0].y <= w[1].y));
}
#[test]
fn edge_cases() {
let layout = Layout::default()
.constraints([
Constraint::Percentage(50),
Constraint::Percentage(50),
Constraint::Min(0),
])
.split(Rect::new(0, 0, 1, 1));
assert_eq!(
layout[..],
[
Rect::new(0, 0, 1, 1),
Rect::new(0, 1, 1, 0),
Rect::new(0, 1, 1, 0)
]
);
let layout = Layout::default()
.constraints([
Constraint::Max(1),
Constraint::Percentage(99),
Constraint::Min(0),
])
.split(Rect::new(0, 0, 1, 1));
assert_eq!(
layout[..],
[
Rect::new(0, 0, 1, 0),
Rect::new(0, 0, 1, 1),
Rect::new(0, 1, 1, 0)
]
);
let layout = Layout::default()
.constraints([Min(1), Length(0), Min(1)])
.direction(Direction::Horizontal)
.split(Rect::new(0, 0, 1, 1));
assert_eq!(
layout[..],
[
Rect::new(0, 0, 1, 1),
Rect::new(1, 0, 0, 1),
Rect::new(1, 0, 0, 1),
]
);
let layout = Layout::default()
.constraints([Length(3), Min(4), Length(1), Min(4)])
.direction(Direction::Horizontal)
.split(Rect::new(0, 0, 7, 1));
assert_eq!(
layout[..],
[
Rect::new(0, 0, 0, 1),
Rect::new(0, 0, 4, 1),
Rect::new(4, 0, 0, 1),
Rect::new(4, 0, 3, 1),
]
);
}
#[rstest]
#[case::length_priority(vec![0, 100], vec![Length(25), Min(100)])]
#[case::length_priority(vec![25, 75], vec![Length(25), Min(0)])]
#[case::length_priority(vec![100, 0], vec![Length(25), Max(0)])]
#[case::length_priority(vec![25, 75], vec![Length(25), Max(100)])]
#[case::length_priority(vec![25, 75], vec![Length(25), Percentage(25)])]
#[case::length_priority(vec![75, 25], vec![Percentage(25), Length(25)])]
#[case::length_priority(vec![25, 75], vec![Length(25), Ratio(1, 4)])]
#[case::length_priority(vec![75, 25], vec![Ratio(1, 4), Length(25)])]
#[case::length_priority(vec![25, 75], vec![Length(25), Length(25)])]
#[case::excess_in_last_variable(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
#[case::excess_in_last_variable(vec![15, 35, 50], vec![Length(15), Length(35), Length(25)])]
#[case::three_lengths(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
fn constraint_length(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::table_length_test(vec![Length(4), Length(4)], vec![(0, 3), (4, 3)], 7)]
#[case::table_length_test(vec![Length(4), Length(4)], vec![(0, 2), (3, 1)], 4)]
fn table_length(
#[case] constraints: Vec<Constraint>,
#[case] expected: Vec<(u16, u16)>,
#[case] width: u16,
) {
let rect = Rect::new(0, 0, width, 1);
let r = Layout::horizontal(constraints)
.spacing(1)
.flex(Flex::Start)
.split(rect)
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::length_is_higher_priority_than_min_max(vec![50, 25, 25], vec![Min(25), Length(25), Max(25)])]
#[case::length_is_higher_priority_than_min_max(vec![25, 25, 50], vec![Max(25), Length(25), Min(25)])]
#[case::excess_in_lowest_priority(vec![33, 33, 34], vec![Length(33), Length(33), Length(33)])]
#[case::excess_in_lowest_priority(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
#[case::length_higher_priority(vec![25, 25, 50], vec![Percentage(25), Length(25), Ratio(1, 4)])]
#[case::length_higher_priority(vec![25, 50, 25], vec![Length(25), Ratio(1, 4), Percentage(25)])]
#[case::length_higher_priority(vec![50, 25, 25], vec![Ratio(1, 4), Length(25), Percentage(25)])]
#[case::length_higher_priority(vec![50, 25, 25], vec![Ratio(1, 4), Percentage(25), Length(25)])]
#[case::length_higher_priority(vec![80, 0, 20], vec![Length(100), Length(1), Min(20)])]
#[case::length_higher_priority(vec![20, 1, 79], vec![Min(20), Length(1), Length(100)])]
#[case::length_higher_priority(vec![45, 10, 45], vec![Fill(1), Length(10), Fill(1)])]
#[case::length_higher_priority(vec![30, 10, 60], vec![Fill(1), Length(10), Fill(2)])]
#[case::length_higher_priority(vec![18, 10, 72], vec![Fill(1), Length(10), Fill(4)])]
#[case::length_higher_priority(vec![15, 10, 75], vec![Fill(1), Length(10), Fill(5)])]
#[case::three_lengths_reference(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
#[case::previously_unstable_test(vec![25, 25, 50], vec![Length(25), Length(25), Length(25)])]
fn length_is_higher_priority(
#[case] expected: Vec<u16>,
#[case] constraints: Vec<Constraint>,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::length_is_higher_priority_than_min_max(vec![50, 25, 25], vec![Min(25), Length(25), Max(25)])]
#[case::length_is_higher_priority_than_min_max(vec![25, 25, 50], vec![Max(25), Length(25), Min(25)])]
#[case::excess_in_lowest_priority(vec![33, 33, 33], vec![Length(33), Length(33), Length(33)])]
#[case::excess_in_lowest_priority(vec![25, 25, 25], vec![Length(25), Length(25), Length(25)])]
#[case::length_higher_priority(vec![25, 25, 25], vec![Percentage(25), Length(25), Ratio(1, 4)])]
#[case::length_higher_priority(vec![25, 25, 25], vec![Length(25), Ratio(1, 4), Percentage(25)])]
#[case::length_higher_priority(vec![25, 25, 25], vec![Ratio(1, 4), Length(25), Percentage(25)])]
#[case::length_higher_priority(vec![25, 25, 25], vec![Ratio(1, 4), Percentage(25), Length(25)])]
#[case::length_higher_priority(vec![79, 1, 20], vec![Length(100), Length(1), Min(20)])]
#[case::length_higher_priority(vec![20, 1, 79], vec![Min(20), Length(1), Length(100)])]
#[case::length_higher_priority(vec![45, 10, 45], vec![Fill(1), Length(10), Fill(1)])]
#[case::length_higher_priority(vec![30, 10, 60], vec![Fill(1), Length(10), Fill(2)])]
#[case::length_higher_priority(vec![18, 10, 72], vec![Fill(1), Length(10), Fill(4)])]
#[case::length_higher_priority(vec![15, 10, 75], vec![Fill(1), Length(10), Fill(5)])]
#[case::previously_unstable_test(vec![25, 25, 25], vec![Length(25), Length(25), Length(25)])]
fn length_is_higher_priority_in_flex(
#[case] expected: Vec<u16>,
#[case] constraints: Vec<Constraint>,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
.flex(Flex::Start)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
.flex(Flex::Center)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
.flex(Flex::End)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
.flex(Flex::SpaceAround)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(&constraints)
.flex(Flex::SpaceBetween)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::excess_in_last_variable(vec![13, 10, 27], vec![Fill(1), Length(10), Fill(2)])]
#[case::excess_in_last_variable(vec![10, 27, 13], vec![Length(10), Fill(2), Fill(1)])] fn fixed_with_50_width(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
let rect = Rect::new(0, 0, 50, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::multiple_same_fill_are_same(vec![20, 40, 20, 20], vec![Fill(1), Fill(2), Fill(1), Fill(1)])]
#[case::incremental(vec![10, 20, 30, 40], vec![Fill(1), Fill(2), Fill(3), Fill(4)])]
#[case::decremental(vec![40, 30, 20, 10], vec![Fill(4), Fill(3), Fill(2), Fill(1)])]
#[case::randomly_ordered(vec![10, 30, 20, 40], vec![Fill(1), Fill(3), Fill(2), Fill(4)])]
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Length(50), Fill(2), Fill(4)])]
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Length(50), Fill(2), Fill(4)])]
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Percentage(50), Fill(2), Fill(4)])]
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Min(50), Fill(2), Fill(4)])]
#[case::randomly_ordered(vec![5, 15, 50, 10, 20], vec![Fill(1), Fill(3), Max(50), Fill(2), Fill(4)])]
#[case::zero_width(vec![0, 100, 0], vec![Fill(0), Fill(1), Fill(0)])]
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Length(1), Fill(0)])]
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Length(1), Fill(0)])]
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Percentage(1), Fill(0)])]
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Min(1), Fill(0)])]
#[case::zero_width(vec![50, 1, 49], vec![Fill(0), Max(1), Fill(0)])]
#[case::zero_width(vec![0, 67, 0, 33], vec![Fill(0), Fill(2), Fill(0), Fill(1)])]
#[case::space_filler(vec![0, 80, 20], vec![Fill(0), Fill(2), Percentage(20)])]
#[case::space_filler(vec![40, 40, 20], vec![Fill(0), Fill(0), Percentage(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(0), Ratio(1, 5)])]
#[case::space_filler(vec![0, 100], vec![Fill(0), Fill(u16::MAX)])]
#[case::space_filler(vec![100, 0], vec![Fill(u16::MAX), Fill(0)])]
#[case::space_filler(vec![80, 20], vec![Fill(0), Percentage(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(1), Percentage(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(u16::MAX), Percentage(20)])]
#[case::space_filler(vec![80, 0, 20], vec![Fill(u16::MAX), Fill(0), Percentage(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(0), Length(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(0), Length(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(0), Min(20)])]
#[case::space_filler(vec![80, 20], vec![Fill(0), Max(20)])]
#[case::fill_collapses_first(vec![7, 6, 7, 30, 50], vec![Fill(1), Fill(1), Fill(1), Min(30), Length(50)])]
#[case::fill_collapses_first(vec![0, 0, 0, 50, 50], vec![Fill(1), Fill(1), Fill(1), Length(50), Length(50)])]
#[case::fill_collapses_first(vec![0, 0, 0, 75, 25], vec![Fill(1), Fill(1), Fill(1), Length(75), Length(50)])]
#[case::fill_collapses_first(vec![0, 0, 0, 50, 50], vec![Fill(1), Fill(1), Fill(1), Min(50), Max(50)])]
#[case::fill_collapses_first(vec![0, 0, 0, 100], vec![Fill(1), Fill(1), Fill(1), Ratio(1, 1)])]
#[case::fill_collapses_first(vec![0, 0, 0, 100], vec![Fill(1), Fill(1), Fill(1), Percentage(100)])]
fn fill(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::min_percentage(vec![80, 20], vec![Min(0), Percentage(20)])]
#[case::max_percentage(vec![0, 100], vec![Max(0), Percentage(20)])]
fn percentage_parameterized(
#[case] expected: Vec<u16>,
#[case] constraints: Vec<Constraint>,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::min_max_priority(vec![100, 0], vec![Max(100), Min(0)])]
#[case::min_max_priority(vec![0, 100], vec![Min(0), Max(100)])]
#[case::min_max_priority(vec![90, 10], vec![Length(u16::MAX), Min(10)])]
#[case::min_max_priority(vec![10, 90], vec![Min(10), Length(u16::MAX)])]
#[case::min_max_priority(vec![90, 10], vec![Length(0), Max(10)])]
#[case::min_max_priority(vec![10, 90], vec![Max(10), Length(0)])]
fn min_max(#[case] expected: Vec<u16>, #[case] constraints: Vec<Constraint>) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| r.width)
.collect::<Vec<u16>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::length(vec![(0, 100)], vec![Length(50)], Flex::Legacy)]
#[case::length(vec![(0, 50)], vec![Length(50)], Flex::Start)]
#[case::length(vec![(50, 50)], vec![Length(50)], Flex::End)]
#[case::length(vec![(25, 50)], vec![Length(50)], Flex::Center)]
#[case::ratio(vec![(0, 100)], vec![Ratio(1, 2)], Flex::Legacy)]
#[case::ratio(vec![(0, 50)], vec![Ratio(1, 2)], Flex::Start)]
#[case::ratio(vec![(50, 50)], vec![Ratio(1, 2)], Flex::End)]
#[case::ratio(vec![(25, 50)], vec![Ratio(1, 2)], Flex::Center)]
#[case::percent(vec![(0, 100)], vec![Percentage(50)], Flex::Legacy)]
#[case::percent(vec![(0, 50)], vec![Percentage(50)], Flex::Start)]
#[case::percent(vec![(50, 50)], vec![Percentage(50)], Flex::End)]
#[case::percent(vec![(25, 50)], vec![Percentage(50)], Flex::Center)]
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Legacy)]
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Start)]
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::End)]
#[case::min(vec![(0, 100)], vec![Min(50)], Flex::Center)]
#[case::max(vec![(0, 100)], vec![Max(50)], Flex::Legacy)]
#[case::max(vec![(0, 50)], vec![Max(50)], Flex::Start)]
#[case::max(vec![(50, 50)], vec![Max(50)], Flex::End)]
#[case::max(vec![(25, 50)], vec![Max(50)], Flex::Center)]
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Min(1)], Flex::SpaceBetween)]
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Max(20)], Flex::SpaceBetween)]
#[case::spacebetween_becomes_stretch(vec![(0, 100)], vec![Length(20)], Flex::SpaceBetween)]
#[case::length(vec![(0, 25), (25, 75)], vec![Length(25), Length(25)], Flex::Legacy)]
#[case::length(vec![(0, 25), (25, 25)], vec![Length(25), Length(25)], Flex::Start)]
#[case::length(vec![(25, 25), (50, 25)], vec![Length(25), Length(25)], Flex::Center)]
#[case::length(vec![(50, 25), (75, 25)], vec![Length(25), Length(25)], Flex::End)]
#[case::length(vec![(0, 25), (75, 25)], vec![Length(25), Length(25)], Flex::SpaceBetween)]
#[case::length(vec![(17, 25), (58, 25)], vec![Length(25), Length(25)], Flex::SpaceAround)]
#[case::percentage(vec![(0, 25), (25, 75)], vec![Percentage(25), Percentage(25)], Flex::Legacy)]
#[case::percentage(vec![(0, 25), (25, 25)], vec![Percentage(25), Percentage(25)], Flex::Start)]
#[case::percentage(vec![(25, 25), (50, 25)], vec![Percentage(25), Percentage(25)], Flex::Center)]
#[case::percentage(vec![(50, 25), (75, 25)], vec![Percentage(25), Percentage(25)], Flex::End)]
#[case::percentage(vec![(0, 25), (75, 25)], vec![Percentage(25), Percentage(25)], Flex::SpaceBetween)]
#[case::percentage(vec![(17, 25), (58, 25)], vec![Percentage(25), Percentage(25)], Flex::SpaceAround)]
#[case::min(vec![(0, 25), (25, 75)], vec![Min(25), Min(25)], Flex::Legacy)]
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::Start)]
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::Center)]
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::End)]
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::SpaceBetween)]
#[case::min(vec![(0, 50), (50, 50)], vec![Min(25), Min(25)], Flex::SpaceAround)]
#[case::max(vec![(0, 25), (25, 75)], vec![Max(25), Max(25)], Flex::Legacy)]
#[case::max(vec![(0, 25), (25, 25)], vec![Max(25), Max(25)], Flex::Start)]
#[case::max(vec![(25, 25), (50, 25)], vec![Max(25), Max(25)], Flex::Center)]
#[case::max(vec![(50, 25), (75, 25)], vec![Max(25), Max(25)], Flex::End)]
#[case::max(vec![(0, 25), (75, 25)], vec![Max(25), Max(25)], Flex::SpaceBetween)]
#[case::max(vec![(17, 25), (58, 25)], vec![Max(25), Max(25)], Flex::SpaceAround)]
#[case::length_spaced_around(vec![(0, 25), (38, 25), (75, 25)], vec![Length(25), Length(25), Length(25)], Flex::SpaceBetween)]
fn flex_constraint(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(flex)
.split(rect)
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::length_spacing(vec![(0 , 20), (20, 20) , (40, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start , 0)]
#[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start , 2)]
#[case::length_spacing(vec![(18, 20), (40, 20) , (62, 20)], vec![Length(20), Length(20), Length(20)], Flex::Center , 2)]
#[case::length_spacing(vec![(36, 20), (58, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End , 2)]
#[case::length_spacing(vec![(0 , 20), (22, 20) , (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy , 2)]
#[case::length_spacing(vec![(0 , 20), (40, 20) , (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceBetween, 2)]
#[case::length_spacing(vec![(10, 20), (40, 20) , (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
fn flex_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
#[case] spacing: u16,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(flex)
.spacing(spacing)
.split(rect);
let result = r
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::a(vec![(0, 25), (25, 75)], vec![Length(25), Length(25)])]
#[case::b(vec![(0, 25), (25, 75)], vec![Length(25), Percentage(25)])]
#[case::c(vec![(0, 75), (75, 25)], vec![Percentage(25), Length(25)])]
#[case::d(vec![(0, 75), (75, 25)], vec![Min(25), Percentage(25)])]
#[case::e(vec![(0, 25), (25, 75)], vec![Percentage(25), Min(25)])]
#[case::f(vec![(0, 25), (25, 75)], vec![Min(25), Percentage(100)])]
#[case::g(vec![(0, 75), (75, 25)], vec![Percentage(100), Min(25)])]
#[case::h(vec![(0, 25), (25, 75)], vec![Max(75), Percentage(75)])]
#[case::i(vec![(0, 75), (75, 25)], vec![Percentage(75), Max(75)])]
#[case::j(vec![(0, 25), (25, 75)], vec![Max(25), Percentage(25)])]
#[case::k(vec![(0, 75), (75, 25)], vec![Percentage(25), Max(25)])]
#[case::l(vec![(0, 25), (25, 75)], vec![Length(25), Ratio(1, 4)])]
#[case::m(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Length(25)])]
#[case::n(vec![(0, 25), (25, 75)], vec![Percentage(25), Ratio(1, 4)])]
#[case::o(vec![(0, 75), (75, 25)], vec![Ratio(1, 4), Percentage(25)])]
#[case::p(vec![(0, 25), (25, 75)], vec![Ratio(1, 4), Fill(25)])]
#[case::q(vec![(0, 75), (75, 25)], vec![Fill(25), Ratio(1, 4)])]
fn constraint_specification_tests_for_priority(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(Flex::Legacy)
.split(rect)
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::a(vec![(0, 20), (20, 20), (40, 20)], vec![Length(20), Length(20), Length(20)], Flex::Start, 0)]
#[case::b(vec![(18, 20), (40, 20), (62, 20)], vec![Length(20), Length(20), Length(20)], Flex::Center, 2)]
#[case::c(vec![(36, 20), (58, 20), (80, 20)], vec![Length(20), Length(20), Length(20)], Flex::End, 2)]
#[case::d(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
#[case::e(vec![(0, 20), (22, 20), (44, 56)], vec![Length(20), Length(20), Length(20)], Flex::Legacy, 2)]
#[case::f(vec![(10, 20), (40, 20), (70, 20)], vec![Length(20), Length(20), Length(20)], Flex::SpaceAround, 2)]
fn constraint_specification_tests_for_priority_with_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
#[case] spacing: u16,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.spacing(spacing)
.flex(flex)
.split(rect)
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, r);
}
#[rstest]
#[case::prop(vec![(0 , 10), (10, 80), (90 , 10)] , vec![Length(10), Fill(1), Length(10)], Flex::Legacy)]
#[case::flex(vec![(0 , 10), (90 , 10)] , vec![Length(10), Length(10)], Flex::SpaceBetween)]
#[case::prop(vec![(0 , 27), (27, 10), (37, 26), (63, 10), (73, 27)] , vec![Fill(1), Length(10), Fill(1), Length(10), Fill(1)], Flex::Legacy)]
#[case::flex(vec![(27 , 10), (63, 10)] , vec![Length(10), Length(10)], Flex::SpaceAround)]
#[case::prop(vec![(0 , 10), (10, 10), (20 , 80)] , vec![Length(10), Length(10), Fill(1)], Flex::Legacy)]
#[case::flex(vec![(0 , 10), (10, 10)] , vec![Length(10), Length(10)], Flex::Start)]
#[case::prop(vec![(0 , 80), (80 , 10), (90, 10)] , vec![Fill(1), Length(10), Length(10)], Flex::Legacy)]
#[case::flex(vec![(80 , 10), (90, 10)] , vec![Length(10), Length(10)], Flex::End)]
#[case::prop(vec![(0 , 40), (40, 10), (50, 10), (60, 40)] , vec![Fill(1), Length(10), Length(10), Fill(1)], Flex::Legacy)]
#[case::flex(vec![(40 , 10), (50, 10)] , vec![Length(10), Length(10)], Flex::Center)]
fn fill_vs_flex(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints).flex(flex).split(rect);
let result = r
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Legacy , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Start , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::Center , 0)]
#[case::flex0(vec![(0 , 50), (50 , 50)] , vec![Fill(1), Fill(1)], Flex::End , 0)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Legacy , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Start , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::Center , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::End , 10)]
#[case::flex10(vec![(10 , 35), (55 , 35)] , vec![Fill(1), Fill(1)], Flex::SpaceAround , 10)]
#[case::flex10(vec![(0 , 45), (55 , 45)] , vec![Fill(1), Fill(1)], Flex::SpaceBetween , 10)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 0)]
#[case::flex_length0(vec![(0 , 45), (45, 10), (55 , 45)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 0)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Legacy , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Start , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::Center , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::End , 10)]
#[case::flex_length10(vec![(10 , 25), (45, 10), (65 , 25)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceAround , 10)]
#[case::flex_length10(vec![(0 , 35), (45, 10), (65 , 35)] , vec![Fill(1), Length(10), Fill(1)], Flex::SpaceBetween , 10)]
fn fill_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
#[case] spacing: u16,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(flex)
.spacing(spacing)
.split(rect);
let result = r
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::flex_length10(vec![(0, 10), (90, 10)], vec![Length(10), Length(10)], Flex::Center, 80)]
fn flex_spacing_lower_priority_than_user_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
#[case] spacing: u16,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints)
.flex(flex)
.spacing(spacing)
.split(rect);
let result = r
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::spacers(vec![(0, 0), (10, 0), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy)]
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween)]
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround)]
#[case::spacers(vec![(0, 0), (10, 0), (20, 80)], vec![Length(10), Length(10)], Flex::Start)]
#[case::spacers(vec![(0, 40), (50, 0), (60, 40)], vec![Length(10), Length(10)], Flex::Center)]
#[case::spacers(vec![(0, 80), (90, 0), (100, 0)], vec![Length(10), Length(10)], Flex::End)]
fn split_with_spacers_no_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
) {
let rect = Rect::new(0, 0, 100, 1);
let (_, s) = Layout::horizontal(&constraints)
.flex(flex)
.split_with_spacers(rect);
assert_eq!(s.len(), constraints.len() + 1);
let result = s
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::spacers(vec![(0, 0), (10, 5), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 5)]
#[case::spacers(vec![(0, 0), (10, 80), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 5)]
#[case::spacers(vec![(0, 27), (37, 26), (73, 27)], vec![Length(10), Length(10)], Flex::SpaceAround, 5)]
#[case::spacers(vec![(0, 0), (10, 5), (25, 75)], vec![Length(10), Length(10)], Flex::Start, 5)]
#[case::spacers(vec![(0, 38), (48, 5), (63, 37)], vec![Length(10), Length(10)], Flex::Center, 5)]
#[case::spacers(vec![(0, 75), (85, 5), (100, 0)], vec![Length(10), Length(10)], Flex::End, 5)]
fn split_with_spacers_and_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
#[case] spacing: u16,
) {
let rect = Rect::new(0, 0, 100, 1);
let (_, s) = Layout::horizontal(&constraints)
.flex(flex)
.spacing(spacing)
.split_with_spacers(rect);
assert_eq!(s.len(), constraints.len() + 1);
let result = s
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Legacy, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::SpaceBetween, 200)]
#[case::spacers(vec![(0, 33), (33, 34), (67, 33)], vec![Length(10), Length(10)], Flex::SpaceAround, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Start, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::Center, 200)]
#[case::spacers(vec![(0, 0), (0, 100), (100, 0)], vec![Length(10), Length(10)], Flex::End, 200)]
fn split_with_spacers_and_too_much_spacing(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
#[case] spacing: u16,
) {
let rect = Rect::new(0, 0, 100, 1);
let (_, s) = Layout::horizontal(&constraints)
.flex(flex)
.spacing(spacing)
.split_with_spacers(rect);
assert_eq!(s.len(), constraints.len() + 1);
let result = s
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
#[rstest]
#[case::compare(vec![(0, 90), (90, 10)], vec![Min(10), Length(10)], Flex::Legacy)]
#[case::compare(vec![(0, 90), (90, 10)], vec![Min(10), Length(10)], Flex::Start)]
#[case::compare(vec![(0, 10), (10, 90)], vec![Min(10), Percentage(100)], Flex::Legacy)]
#[case::compare(vec![(0, 10), (10, 90)], vec![Min(10), Percentage(100)], Flex::Start)]
#[case::compare(vec![(0, 50), (50, 50)], vec![Percentage(50), Percentage(50)], Flex::Legacy)]
#[case::compare(vec![(0, 50), (50, 50)], vec![Percentage(50), Percentage(50)], Flex::Start)]
fn legacy_vs_default(
#[case] expected: Vec<(u16, u16)>,
#[case] constraints: Vec<Constraint>,
#[case] flex: Flex,
) {
let rect = Rect::new(0, 0, 100, 1);
let r = Layout::horizontal(constraints).flex(flex).split(rect);
let result = r
.iter()
.map(|r| (r.x, r.width))
.collect::<Vec<(u16, u16)>>();
assert_eq!(expected, result);
}
}
#[test]
fn test_solver() {
use super::*;
let mut solver = Solver::new();
let x = Variable::new();
let y = Variable::new();
solver.add_constraint((x + y) | EQ(4.0) | 5.0).unwrap();
solver.add_constraint(x | EQ(1.0) | 2.0).unwrap();
for _ in 0..5 {
solver.add_constraint(y | EQ(1.0) | 2.0).unwrap();
}
let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
let x = changes.get(&x).unwrap_or(&0.0).round() as u16;
let y = changes.get(&y).unwrap_or(&0.0).round() as u16;
assert_eq!(x, 3);
assert_eq!(y, 2);
let mut solver = Solver::new();
let x = Variable::new();
let y = Variable::new();
solver.add_constraint((x + y) | EQ(4.0) | 5.0).unwrap();
solver.add_constraint(y | EQ(1.0) | 2.0).unwrap();
for _ in 0..5 {
solver.add_constraint(x | EQ(1.0) | 2.0).unwrap();
}
let changes: HashMap<Variable, f64> = solver.fetch_changes().iter().copied().collect();
let x = changes.get(&x).unwrap_or(&0.0).round() as u16;
let y = changes.get(&y).unwrap_or(&0.0).round() as u16;
assert_eq!(x, 2);
assert_eq!(y, 3);
}
}