use crate::geom::{Rect, Size};
use crate::style::Length;
#[derive(Debug, Clone, Copy)]
pub struct Constraint {
pub min: Size,
pub max: Size,
}
impl Constraint {
pub fn loose(max_width: u16, max_height: u16) -> Self {
Self {
min: Size::default(),
max: Size {
width: max_width,
height: max_height,
},
}
}
}
pub struct LayoutItem {
pub width: Length,
pub height: Length,
}
pub fn layout_vertical(rect: Rect, items: &[LayoutItem], gap: u16) -> Vec<Rect> {
let mut rects = Vec::with_capacity(items.len());
if items.is_empty() {
return rects;
}
let total_gap = gap.saturating_mul(items.len().saturating_sub(1) as u16);
let available_height = rect.height.saturating_sub(total_gap);
let mut fixed_total: u16 = 0;
let mut fraction_total: u16 = 0;
let mut auto_count: u16 = 0;
for item in items {
match item.height {
Length::Fixed(h) => fixed_total = fixed_total.saturating_add(h),
Length::Fraction(w) => fraction_total = fraction_total.saturating_add(w),
Length::Percent(_) | Length::Auto => auto_count = auto_count.saturating_add(1),
}
}
let fixed_total = fixed_total.min(available_height);
let auto_total = auto_count;
let fraction_space = available_height
.saturating_sub(fixed_total)
.saturating_sub(auto_total);
let fraction_unit = if fraction_total > 0 {
fraction_space / fraction_total
} else {
0
};
let mut y_offset = rect.y;
for item in items {
let height = match item.height {
Length::Fixed(h) => h.min(available_height),
Length::Percent(p) => available_height.saturating_mul(p) / 100,
Length::Fraction(w) => {
let base = w.saturating_mul(fraction_unit);
fraction_total = fraction_total.saturating_sub(w);
if fraction_total == 0 {
rect.y.saturating_add(available_height).saturating_sub(y_offset)
} else {
base
}
}
Length::Auto => 1,
};
let height = if y_offset.saturating_add(height) > rect.y.saturating_add(available_height) {
rect.y.saturating_add(available_height).saturating_sub(y_offset)
} else {
height
};
rects.push(Rect {
x: rect.x,
y: y_offset,
width: rect.width,
height,
});
y_offset = y_offset.saturating_add(height).saturating_add(gap);
}
rects
}
pub fn layout_horizontal(rect: Rect, items: &[LayoutItem], gap: u16) -> Vec<Rect> {
let mut rects = Vec::with_capacity(items.len());
if items.is_empty() {
return rects;
}
let total_gap = gap.saturating_mul(items.len().saturating_sub(1) as u16);
let available_width = rect.width.saturating_sub(total_gap);
let mut fixed_total: u16 = 0;
let mut fraction_total: u16 = 0;
let mut auto_count: u16 = 0;
for item in items {
match item.width {
Length::Fixed(w) => fixed_total = fixed_total.saturating_add(w),
Length::Fraction(w) => fraction_total = fraction_total.saturating_add(w),
Length::Percent(_) | Length::Auto => auto_count = auto_count.saturating_add(1),
}
}
let fixed_total = fixed_total.min(available_width);
let auto_total = auto_count;
let fraction_space = available_width
.saturating_sub(fixed_total)
.saturating_sub(auto_total);
let fraction_unit = if fraction_total > 0 {
fraction_space / fraction_total
} else {
0
};
let mut x_offset = rect.x;
for item in items {
let width = match item.width {
Length::Fixed(w) => w.min(available_width),
Length::Percent(p) => available_width.saturating_mul(p) / 100,
Length::Fraction(w) => {
let base = w.saturating_mul(fraction_unit);
fraction_total = fraction_total.saturating_sub(w);
if fraction_total == 0 {
rect.x.saturating_add(available_width).saturating_sub(x_offset)
} else {
base
}
}
Length::Auto => 1,
};
let width = if x_offset.saturating_add(width) > rect.x.saturating_add(available_width) {
rect.x.saturating_add(available_width).saturating_sub(x_offset)
} else {
width
};
rects.push(Rect {
x: x_offset,
y: rect.y,
width,
height: rect.height,
});
x_offset = x_offset.saturating_add(width).saturating_add(gap);
}
rects
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layout_vertical_fixed() {
let rect = Rect { x: 0, y: 0, width: 10, height: 10 };
let items = [
LayoutItem { width: Length::Auto, height: Length::Fixed(3) },
LayoutItem { width: Length::Auto, height: Length::Fixed(4) },
];
let result = layout_vertical(rect, &items, 1);
assert_eq!(result.len(), 2);
assert_eq!(result[0], Rect { x: 0, y: 0, width: 10, height: 3 });
assert_eq!(result[1], Rect { x: 0, y: 4, width: 10, height: 4 });
}
#[test]
fn test_layout_horizontal_fixed() {
let rect = Rect { x: 0, y: 0, width: 10, height: 5 };
let items = [
LayoutItem { width: Length::Fixed(3), height: Length::Auto },
LayoutItem { width: Length::Fixed(4), height: Length::Auto },
];
let result = layout_horizontal(rect, &items, 1);
assert_eq!(result.len(), 2);
assert_eq!(result[0], Rect { x: 0, y: 0, width: 3, height: 5 });
assert_eq!(result[1], Rect { x: 4, y: 0, width: 4, height: 5 });
}
#[test]
fn test_layout_horizontal_fraction() {
let rect = Rect { x: 0, y: 0, width: 10, height: 5 };
let items = [
LayoutItem { width: Length::Fraction(1), height: Length::Auto },
LayoutItem { width: Length::Fraction(1), height: Length::Auto },
];
let result = layout_horizontal(rect, &items, 0);
assert_eq!(result.len(), 2);
assert_eq!(result[0].width, 5);
assert_eq!(result[1].width, 5);
}
}