use crate::core::rect::Rect;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Constraint {
Length(u16),
Min(u16),
Max(u16),
Percentage(u16),
Ratio(u32, u32),
Fill(u16),
}
impl Constraint {
fn apply(&self, total: u16) -> u16 {
match *self {
Constraint::Length(l) => l.min(total),
Constraint::Min(m) => total.max(m),
Constraint::Max(m) => total.min(m),
Constraint::Percentage(p) => (total as f32 * p as f32 / 100.0) as u16,
Constraint::Ratio(a, b) => {
if b == 0 {
0
} else {
(total as f32 * a as f32 / b as f32) as u16
}
}
Constraint::Fill(gap) => {
if gap == 0 {
total
} else {
(total / gap).max(1) * gap
}
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Direction {
Horizontal,
Vertical,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Layout {
pub direction: Direction,
pub constraints: Vec<Constraint>,
pub margin: Rect,
}
impl Layout {
pub fn default() -> Self {
Self {
direction: Direction::Vertical,
constraints: Vec::new(),
margin: Rect::ZERO,
}
}
pub fn horizontal(constraints: Vec<Constraint>) -> Self {
Self {
direction: Direction::Horizontal,
constraints,
margin: Rect::ZERO,
}
}
pub fn vertical(constraints: Vec<Constraint>) -> Self {
Self {
direction: Direction::Vertical,
constraints,
margin: Rect::ZERO,
}
}
pub fn margin(mut self, margin: Rect) -> Self {
self.margin = margin;
self
}
pub fn split(&self, area: Rect) -> Vec<Rect> {
let inner = area.inner(self.margin);
match self.direction {
Direction::Horizontal => self.split_horizontal(inner),
Direction::Vertical => self.split_vertical(inner),
}
}
fn split_horizontal(&self, area: Rect) -> Vec<Rect> {
let total = area.width;
let gap = self.margin.x;
let mut remaining = total;
let mut positions = Vec::with_capacity(self.constraints.len());
for constraint in &self.constraints {
let size = constraint.apply(remaining);
positions.push(size);
remaining = remaining.saturating_sub(size + gap);
}
let mut rects = Vec::with_capacity(self.constraints.len());
let mut x = area.x;
for &size in &positions {
rects.push(Rect::new(x, area.y, size, area.height));
x = x.saturating_add(size + gap);
}
rects
}
fn split_vertical(&self, area: Rect) -> Vec<Rect> {
let total = area.height;
let gap = self.margin.y;
let mut remaining = total;
let mut positions = Vec::with_capacity(self.constraints.len());
for constraint in &self.constraints {
let size = constraint.apply(remaining);
positions.push(size);
remaining = remaining.saturating_sub(size + gap);
}
let mut rects = Vec::with_capacity(self.constraints.len());
let mut y = area.y;
for &size in &positions {
rects.push(Rect::new(area.x, y, area.width, size));
y = y.saturating_add(size + gap);
}
rects
}
}
impl Default for Layout {
fn default() -> Self {
Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constraint_length() {
assert_eq!(Constraint::Length(5).apply(20), 5);
assert_eq!(Constraint::Length(25).apply(20), 20);
}
#[test]
fn test_constraint_percentage() {
assert_eq!(Constraint::Percentage(50).apply(20), 10);
assert_eq!(Constraint::Percentage(100).apply(20), 20);
}
#[test]
fn test_layout_split_vertical() {
let layout = Layout::vertical(vec![
Constraint::Length(3),
Constraint::Length(5),
Constraint::Min(0),
]);
let area = Rect::new(0, 0, 40, 20);
let rects = layout.split(area);
assert_eq!(rects.len(), 3);
assert_eq!(rects[0].height, 3);
assert_eq!(rects[1].height, 5);
assert_eq!(rects[2].y, 8);
}
#[test]
fn test_layout_split_horizontal() {
let layout = Layout::horizontal(vec![
Constraint::Percentage(33),
Constraint::Percentage(33),
Constraint::Min(0),
]);
let area = Rect::new(0, 0, 30, 10);
let rects = layout.split(area);
assert_eq!(rects.len(), 3);
assert_eq!(rects[0].width, 9);
assert_eq!(rects[0].height, 10);
}
}