#![forbid(unsafe_code)]
use stipple_geometry::Size;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Axis {
Horizontal,
Vertical,
}
impl Axis {
#[inline]
pub fn main(self, size: Size) -> f64 {
match self {
Axis::Horizontal => size.width,
Axis::Vertical => size.height,
}
}
#[inline]
pub fn cross(self, size: Size) -> f64 {
match self {
Axis::Horizontal => size.height,
Axis::Vertical => size.width,
}
}
#[inline]
pub fn size(self, main: f64, cross: f64) -> Size {
match self {
Axis::Horizontal => Size::new(main, cross),
Axis::Vertical => Size::new(cross, main),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Constraints {
pub min: Size,
pub max: Size,
}
impl Constraints {
#[inline]
pub fn loose(max: Size) -> Self {
Self {
min: Size::ZERO,
max,
}
}
#[inline]
pub fn tight(size: Size) -> Self {
Self {
min: size,
max: size,
}
}
#[inline]
pub fn constrain(&self, size: Size) -> Size {
size.clamp(self.min, self.max)
}
pub fn deflate(&self, horizontal: f64, vertical: f64) -> Self {
let max = Size::new(
(self.max.width - horizontal).max(0.0),
(self.max.height - vertical).max(0.0),
);
Self {
min: self.min.clamp(Size::ZERO, max),
max,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FlexItem {
pub basis: f64,
pub grow: f64,
}
impl FlexItem {
#[inline]
pub fn fixed(basis: f64) -> Self {
Self { basis, grow: 0.0 }
}
#[inline]
pub fn flex(grow: f64) -> Self {
Self { basis: 0.0, grow }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Span {
pub offset: f64,
pub length: f64,
}
pub fn solve_main_axis(available: f64, gap: f64, items: &[FlexItem]) -> Vec<Span> {
if items.is_empty() {
return Vec::new();
}
let total_gap = gap * (items.len() as f64 - 1.0);
let total_basis: f64 = items.iter().map(|i| i.basis).sum();
let total_grow: f64 = items.iter().map(|i| i.grow).sum();
let free = (available - total_basis - total_gap).max(0.0);
let mut spans = Vec::with_capacity(items.len());
let mut cursor = 0.0;
for item in items {
let extra = if total_grow > 0.0 {
free * (item.grow / total_grow)
} else {
0.0
};
let length = item.basis + extra;
spans.push(Span {
offset: cursor,
length,
});
cursor += length + gap;
}
spans
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fixed_items_pack_with_gap() {
let spans = solve_main_axis(200.0, 10.0, &[FlexItem::fixed(40.0), FlexItem::fixed(60.0)]);
assert_eq!(
spans[0],
Span {
offset: 0.0,
length: 40.0
}
);
assert_eq!(
spans[1],
Span {
offset: 50.0,
length: 60.0
}
);
}
#[test]
fn grow_distributes_free_space() {
let spans = solve_main_axis(
200.0,
20.0,
&[
FlexItem::fixed(40.0),
FlexItem::flex(1.0),
FlexItem::flex(1.0),
],
);
assert_eq!(spans[0].length, 40.0);
assert_eq!(spans[1].length, 60.0);
assert_eq!(spans[2].length, 60.0);
assert_eq!(spans[1].offset, 60.0);
assert_eq!(spans[2].offset, 140.0);
}
#[test]
fn overflow_clamps_free_to_zero() {
let spans = solve_main_axis(30.0, 0.0, &[FlexItem::fixed(40.0), FlexItem::flex(1.0)]);
assert_eq!(spans[1].length, 0.0);
}
#[test]
fn axis_main_cross_roundtrip() {
let s = Axis::Vertical.size(10.0, 4.0);
assert_eq!(s, Size::new(4.0, 10.0));
assert_eq!(Axis::Vertical.main(s), 10.0);
assert_eq!(Axis::Vertical.cross(s), 4.0);
}
}