#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Constraint {
Length(u16),
Ratio(u32, u32),
Min(u16),
Max(u16),
Fill,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Vertical,
Horizontal,
}
pub struct Layout {
direction: Direction,
constraints: Vec<Constraint>,
}
impl Layout {
pub fn vertical(constraints: Vec<Constraint>) -> Self {
Self { direction: Direction::Vertical, constraints }
}
pub fn horizontal(constraints: Vec<Constraint>) -> Self {
Self { direction: Direction::Horizontal, constraints }
}
pub fn split(&self, area: Rect) -> Vec<Rect> {
match self.direction {
Direction::Vertical => self.split_vertical(area),
Direction::Horizontal => self.split_horizontal(area),
}
}
fn split_vertical(&self, area: Rect) -> Vec<Rect> {
let mut result = Vec::new();
let mut remaining_height = area.height;
let mut y = area.y;
let mut fixed_length = 0;
let mut min_length = 0;
for constraint in &self.constraints {
match constraint {
Constraint::Length(len) => fixed_length += len,
Constraint::Min(len) => min_length += len,
_ => {}
}
}
let available_space = remaining_height.saturating_sub(fixed_length);
let mut ratio_sum = 0;
for constraint in &self.constraints {
if let Constraint::Ratio(numerator, denominator) = constraint {
ratio_sum += numerator * 1000 / denominator;
}
}
for constraint in &self.constraints {
let height = match constraint {
Constraint::Length(len) => *len,
Constraint::Min(len) => *len,
Constraint::Max(len) => std::cmp::min(*len, remaining_height),
Constraint::Ratio(numerator, denominator) => {
if ratio_sum > 0 {
(available_space as u32 * numerator * 1000 / denominator / ratio_sum) as u16
}
else {
0
}
}
Constraint::Fill => remaining_height,
};
if height > 0 {
result.push(Rect { x: area.x, y, width: area.width, height });
y += height;
remaining_height -= height;
}
}
result
}
fn split_horizontal(&self, area: Rect) -> Vec<Rect> {
let mut result = Vec::new();
let mut remaining_width = area.width;
let mut x = area.x;
let mut fixed_length = 0;
let mut min_length = 0;
for constraint in &self.constraints {
match constraint {
Constraint::Length(len) => fixed_length += len,
Constraint::Min(len) => min_length += len,
_ => {}
}
}
let available_space = remaining_width.saturating_sub(fixed_length);
let mut ratio_sum = 0;
for constraint in &self.constraints {
if let Constraint::Ratio(numerator, denominator) = constraint {
ratio_sum += numerator * 1000 / denominator;
}
}
for constraint in &self.constraints {
let width = match constraint {
Constraint::Length(len) => *len,
Constraint::Min(len) => *len,
Constraint::Max(len) => std::cmp::min(*len, remaining_width),
Constraint::Ratio(numerator, denominator) => {
if ratio_sum > 0 {
(available_space as u32 * numerator * 1000 / denominator / ratio_sum) as u16
}
else {
0
}
}
Constraint::Fill => remaining_width,
};
if width > 0 {
result.push(Rect { x, y: area.y, width, height: area.height });
x += width;
remaining_width -= width;
}
}
result
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Rect {
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self { x, y, width, height }
}
pub fn from_size(width: u16, height: u16) -> Self {
Self { x: 0, y: 0, width, height }
}
pub fn contains(&self, x: u16, y: u16) -> bool {
x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
}
pub fn shrink(&self, margin: u16) -> Self {
Self {
x: self.x + margin,
y: self.y + margin,
width: self.width.saturating_sub(2 * margin),
height: self.height.saturating_sub(2 * margin),
}
}
}