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 margin: crate::geom::Insets,
pub flex_grow: u16,
pub flex_shrink: bool,
}
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 margin_v: u16 = items.iter().map(|item| item.margin.top.saturating_add(item.margin.bottom)).sum();
let available_height = rect.height.saturating_sub(total_gap).saturating_sub(margin_v);
let mut fixed_total: u16 = 0;
let mut fraction_total: u16 = 0;
let mut auto_count: u16 = 0;
for item in items {
let h = if item.flex_grow > 0 { Length::Fraction(item.flex_grow) } else { item.height };
match h {
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 fraction_remaining = fraction_total;
let mut y_offset = rect.y;
for item in items {
y_offset = y_offset.saturating_add(item.margin.top);
let h = if item.flex_grow > 0 { Length::Fraction(item.flex_grow) } else { item.height };
let height = match h {
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_remaining = fraction_remaining.saturating_sub(w);
if fraction_remaining == 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.saturating_add(item.margin.left),
y: y_offset,
width: rect.width.saturating_sub(item.margin.left.saturating_add(item.margin.right)),
height,
});
y_offset = y_offset.saturating_add(height).saturating_add(item.margin.bottom).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 margin_h: u16 = items.iter().map(|item| item.margin.left.saturating_add(item.margin.right)).sum();
let available_width = rect.width.saturating_sub(total_gap).saturating_sub(margin_h);
let mut fixed_total: u16 = 0;
let mut fraction_total: u16 = 0;
let mut auto_count: u16 = 0;
for item in items {
let w = if item.flex_grow > 0 { Length::Fraction(item.flex_grow) } else { item.width };
match w {
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 fraction_remaining = fraction_total;
let mut x_offset = rect.x;
for item in items {
x_offset = x_offset.saturating_add(item.margin.left);
let w = if item.flex_grow > 0 { Length::Fraction(item.flex_grow) } else { item.width };
let width = match w {
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_remaining = fraction_remaining.saturating_sub(w);
if fraction_remaining == 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.saturating_add(item.margin.top),
width,
height: rect.height.saturating_sub(item.margin.top.saturating_add(item.margin.bottom)),
});
x_offset = x_offset.saturating_add(width).saturating_add(item.margin.right).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), margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true },
LayoutItem { width: Length::Auto, height: Length::Fixed(4), margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true },
];
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, margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true },
LayoutItem { width: Length::Fixed(4), height: Length::Auto, margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true },
];
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, margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true },
LayoutItem { width: Length::Fraction(1), height: Length::Auto, margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true },
];
let result = layout_horizontal(rect, &items, 0);
assert_eq!(result.len(), 2);
assert_eq!(result[0].width, 5);
assert_eq!(result[1].width, 5);
}
#[test]
fn bench_layout_large() {
let rect = Rect { x: 0, y: 0, width: 200, height: 200 };
let items: Vec<LayoutItem> = (0..100).map(|_| LayoutItem {
width: Length::Fraction(1), height: Length::Fixed(1),
margin: crate::geom::Insets::ZERO, flex_grow: 0, flex_shrink: true,
}).collect();
let result = layout_vertical(rect, &items, 0);
assert_eq!(result.len(), 100);
for r in &result { assert!(r.height > 0); }
}
}