use std::{
cmp::{max, min},
hash::{DefaultHasher, Hash, Hasher},
};
mod layout_node;
mod layouting;
pub use layout_node::LayoutNode;
pub use layouting::*;
use crate::{
buffer::Buffer,
enums::Color,
geometry::Padding,
prelude::{Constraint, Direction, Rect, Vec2},
style::Style,
widgets::{Element, Widget},
};
#[derive(Debug)]
pub struct Layout<M: 'static = ()> {
direction: Direction,
children: Vec<Element<M>>,
constraints: Vec<Constraint>,
style: Style,
padding: Padding,
center: bool,
}
impl<M> Layout<M> {
#[must_use]
pub fn new(direction: Direction) -> Self {
Self {
direction,
..Default::default()
}
}
#[must_use]
pub fn vertical() -> Self {
Default::default()
}
#[must_use]
pub fn horizontal() -> Self {
Self {
direction: Direction::Horizontal,
..Default::default()
}
}
#[must_use]
pub fn direction(mut self, direction: Direction) -> Self {
self.direction = direction;
self
}
#[must_use]
pub fn style<T>(mut self, style: T) -> Self
where
T: Into<Style>,
{
self.style = style.into();
self
}
#[must_use]
pub fn bg<T>(mut self, bg: T) -> Self
where
T: Into<Option<Color>>,
{
self.style = self.style.bg(bg);
self
}
#[must_use]
pub fn fg<T>(mut self, fg: T) -> Self
where
T: Into<Option<Color>>,
{
self.style = self.style.fg(fg);
self
}
#[must_use]
pub fn padding<T>(mut self, padding: T) -> Self
where
T: Into<Padding>,
{
self.padding = padding.into();
self
}
#[must_use]
pub fn center(mut self) -> Self {
self.center = true;
self
}
pub fn push<T, C>(&mut self, child: T, constraint: C)
where
T: Into<Element<M>>,
C: Into<Constraint>,
{
self.children.push(child.into());
self.constraints.push(constraint.into());
}
}
impl<M: Clone + 'static> Widget<M> for Layout<M> {
fn render(&self, buffer: &mut Buffer, layout: &LayoutNode) {
self.render_base_style(buffer, &layout.area);
for (i, child) in self.children.iter().enumerate() {
child.render(buffer, &layout.children[i]);
}
}
fn height(&self, size: &Vec2) -> usize {
let size = Vec2::new(
size.x.saturating_sub(self.padding.get_horizontal()),
size.y.saturating_sub(self.padding.get_vertical()),
);
let height = match self.direction {
Direction::Vertical => {
self.size_sd(&size, size.y, |c, s| c.height(s))
}
Direction::Horizontal => self.hor_height(&size),
};
height + self.padding.get_vertical()
}
fn width(&self, size: &Vec2) -> usize {
let size = Vec2::new(
size.x.saturating_sub(self.padding.get_horizontal()),
size.y.saturating_sub(self.padding.get_vertical()),
);
let width = match self.direction {
Direction::Vertical => self.ver_width(&size),
Direction::Horizontal => {
self.size_sd(&size, size.x, |c, s| c.width(s))
}
};
width + self.padding.get_horizontal()
}
fn children(&self) -> Vec<&Element<M>> {
self.children.iter().collect()
}
fn layout_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.direction.hash(&mut hasher);
self.constraints.hash(&mut hasher);
self.padding.hash(&mut hasher);
self.center.hash(&mut hasher);
hasher.finish()
}
fn layout(&self, node: &mut LayoutNode, area: Rect) {
if !node.is_dirty && !node.has_dirty_child && node.area == area {
return;
}
node.area = area;
let rect = area.inner(self.padding);
match self.direction {
Direction::Vertical => self.layout_ver(node, rect),
Direction::Horizontal => self.layout_hor(node, rect),
};
node.is_dirty = false;
node.has_dirty_child = false;
}
}
impl<M> Default for Layout<M> {
fn default() -> Self {
Self {
direction: Direction::Vertical,
children: Vec::new(),
constraints: Vec::new(),
style: Style::new(),
padding: Default::default(),
center: false,
}
}
}
impl<M: Clone + 'static> Layout<M> {
fn layout_ver(&self, node: &mut LayoutNode, area: Rect) {
let (sizes, mut rect) = self.ver_sizes(area);
for (i, s) in sizes.iter().enumerate() {
let csize = min(*s, rect.height());
let crect =
Rect::from_coords(*rect.pos(), Vec2::new(rect.width(), csize));
self.children[i].layout(&mut node.children[i], crect);
rect = rect.inner(Padding::top(csize));
}
}
fn layout_hor(&self, node: &mut LayoutNode, area: Rect) {
let (sizes, mut rect) = self.hor_sizes(area);
for (i, s) in sizes.iter().enumerate() {
let csize = min(*s, rect.width());
let crect = Rect::from_coords(
*rect.pos(),
Vec2::new(csize, rect.height()),
);
self.children[i].layout(&mut node.children[i], crect);
rect = rect.inner(Padding::left(csize));
}
}
fn ver_sizes(&self, rect: Rect) -> (Vec<usize>, Rect) {
self.child_sizes(
rect,
rect.height(),
|c, s| c.height(s),
|s, v| s.y = s.y.saturating_sub(v),
|s| s.y,
|r, s| r.inner(Padding::vertical(s)),
)
}
fn hor_sizes(&self, rect: Rect) -> (Vec<usize>, Rect) {
self.child_sizes(
rect,
rect.width(),
|c, s| c.width(s),
|s, v| s.x = s.x.saturating_sub(v),
|s| s.x,
|r, s| r.inner(Padding::horizontal(s)),
)
}
fn child_sizes<F1, F2, F3, F4>(
&self,
rect: Rect,
percent: usize,
csize: F1,
shrink: F2,
left: F3,
inner: F4,
) -> (Vec<usize>, Rect)
where
F1: Fn(&Element<M>, &Vec2) -> usize,
F2: Fn(&mut Vec2, usize),
F3: Fn(Vec2) -> usize,
F4: Fn(Rect, usize) -> Rect,
{
let mut fill_ids = Vec::new();
let mut fills = 0;
let mut sizes = Vec::new();
let mut size = *rect.size();
for (i, constraint) in self.constraints.iter().enumerate() {
let csize = match constraint {
Constraint::Length(len) => *len,
Constraint::Percent(p) => percent * p / 100,
Constraint::Min(l) => max(csize(&self.children[i], &size), *l),
Constraint::Max(h) => min(csize(&self.children[i], &size), *h),
Constraint::MinMax(l, h) => {
min(max(csize(&self.children[i], &size), *l), *h)
}
Constraint::Fill(val) => {
fill_ids.push(sizes.len());
sizes.push(*val);
fills += val;
continue;
}
};
sizes.push(csize);
shrink(&mut size, csize);
}
let mut left = left(size);
if fills == 0 && self.center {
return (sizes, inner(rect, left / 2));
}
for f in fill_ids {
let fill = sizes[f];
sizes[f] = left / fills * fill;
fills -= fill;
left -= sizes[f];
}
(sizes, rect)
}
fn render_base_style(&self, buffer: &mut Buffer, rect: &Rect) {
for pos in rect.into_iter() {
buffer.set_style(self.style, &pos);
if self.style.bg.is_some() {
buffer.set_char(' ', &pos);
}
}
}
fn size_sd<F>(&self, size: &Vec2, prim: usize, csize: F) -> usize
where
F: Fn(&Element<M>, &Vec2) -> usize,
{
let mut total = 0;
let mut fill = false;
for (i, constraint) in self.constraints.iter().enumerate() {
match constraint {
Constraint::Length(len) => total += len,
Constraint::Percent(p) => total += prim * p / 100,
Constraint::Min(l) => {
total += max(*l, csize(&self.children[i], size))
}
Constraint::Max(h) => {
total += min(*h, csize(&self.children[i], size))
}
Constraint::MinMax(l, h) => {
total += min(*h, max(*l, csize(&self.children[i], size)))
}
Constraint::Fill(_) => fill = true,
}
}
if fill {
return max(prim, total);
}
total
}
fn ver_width(&self, size: &Vec2) -> usize {
let mut width = 0;
let mut total = 0;
let mut total_fills = 0;
let mut fills = Vec::new();
for (i, constraint) in self.constraints.iter().enumerate() {
let csize = match constraint {
Constraint::Length(len) => *len,
Constraint::Percent(p) => size.y * p / 100,
Constraint::Min(l) => max(*l, self.children[i].height(size)),
Constraint::Max(h) => min(*h, self.children[i].height(size)),
Constraint::MinMax(l, h) => {
min(*h, max(*l, self.children[i].height(size)))
}
Constraint::Fill(f) => {
total_fills += f;
fills.push((&self.children[i], f));
continue;
}
};
total += csize;
width =
width.max(self.children[i].width(&Vec2::new(size.x, csize)));
}
let mut left = Vec2::new(size.x, size.y.saturating_sub(total));
for (child, f) in fills {
let h = left.y / total_fills * f;
width = width.max(child.width(&left));
left.y -= h;
total_fills -= f;
}
width
}
fn hor_height(&self, size: &Vec2) -> usize {
let mut height = 0;
let mut total = 0;
let mut total_fills = 0;
let mut fills = Vec::new();
for (i, constraint) in self.constraints.iter().enumerate() {
let csize = match constraint {
Constraint::Length(len) => *len,
Constraint::Percent(p) => size.y * p / 100,
Constraint::Min(l) => max(*l, self.children[i].width(size)),
Constraint::Max(h) => min(*h, self.children[i].width(size)),
Constraint::MinMax(l, h) => {
min(*h, max(*l, self.children[i].width(size)))
}
Constraint::Fill(f) => {
total_fills += f;
fills.push((&self.children[i], f));
continue;
}
};
total += csize;
height =
height.max(self.children[i].height(&Vec2::new(csize, size.y)));
}
let mut left = Vec2::new(size.x, size.y.saturating_sub(total));
for (child, f) in fills {
let h = left.y / total_fills * f;
height = height.max(child.width(&left));
left.y -= h;
total_fills -= f;
}
height
}
}
impl<M: Clone + 'static> From<Layout<M>> for Box<dyn Widget<M>> {
fn from(value: Layout<M>) -> Self {
Box::new(value)
}
}
impl<M: Clone + 'static> From<Layout<M>> for Element<M> {
fn from(value: Layout<M>) -> Self {
Element::new(value)
}
}