use std::{error::Error, fmt::Display};
use tui::{
layout::{Constraint, Rect},
style::Style,
symbols::line::Set,
widgets::{BorderType, Widget},
};
#[derive(Clone)]
pub struct Grid {
pub widths: Vec<Constraint>,
pub heights: Vec<Constraint>,
pub border_type: BorderType,
pub border_style: Style,
}
impl Grid {
pub fn new(widths: Vec<Constraint>, heights: Vec<Constraint>) -> Result<Self, GridError> {
if widths.len() == 0 || heights.len() == 0 {
return Err(GridError::ZeroLength);
}
Ok(Self {
widths,
heights,
border_type: BorderType::Plain,
border_style: Style::default(),
})
}
}
impl Grid {
pub fn chunks(&self, area: Rect) -> Result<Vec<Vec<Rect>>, GridError> {
let widths = self.widths(area.width)?;
let heights = self.heights(area.height)?;
let xs = {
let mut xs = Self::lines(area.x, &widths);
xs.truncate(self.widths.len());
xs.iter_mut().for_each(|item| *item += 1);
xs
};
let ys = {
let mut ys = Self::lines(area.y, &heights);
ys.truncate(self.heights.len());
ys.iter_mut().for_each(|item| *item += 1);
ys
};
Ok(ys
.iter()
.zip(heights.iter())
.map(|(y, height)| {
xs.iter()
.zip(widths.iter())
.map(|(x, width)| Rect::new(*x, *y, *width, *height))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>())
}
pub fn lines(mut position: u16, lengths: &Vec<u16>) -> Vec<u16> {
let mut lines = Vec::new();
lengths.iter().for_each(|lengths| {
lines.push(position);
position += 1 + *lengths;
});
position -= 1;
lines.push(position);
lines
}
pub fn heights(&self, height: u16) -> Result<Vec<u16>, GridError> {
Self::lengths(&self.heights, height)
}
pub fn widths(&self, width: u16) -> Result<Vec<u16>, GridError> {
Self::lengths(&self.widths, width)
}
pub fn lengths(constraints: &Vec<Constraint>, mut length: u16) -> Result<Vec<u16>, GridError> {
if length < constraints.len() as u16 + 1 {
return Err(GridError::NotEnoughLength);
}
length -= constraints.len() as u16;
let lengths = constraints
.iter()
.map(|constraint| constraint.apply(length))
.collect::<Vec<_>>();
Ok(lengths)
}
}
impl Grid {
pub fn from_pos<'a>(
x: &'a u16,
y: &'a u16,
left: &'a u16,
right: &'a u16,
top: &'a u16,
bottom: &'a u16,
set: &'a Set,
) -> &'a str {
let is_top = y == top;
let is_bottom = y == bottom;
let is_left = x == left;
let is_right = x == right;
if is_top {
if is_left {
return set.top_left;
}
if is_right {
return set.top_right;
}
return set.horizontal_down;
}
if is_bottom {
if is_left {
return set.bottom_left;
}
if is_right {
return set.bottom_right;
}
return set.horizontal_up;
}
if is_left {
return set.vertical_right;
}
if is_right {
return set.vertical_left;
}
return set.cross;
}
}
impl Grid {
pub fn border_type(mut self, border_type: BorderType) -> Self {
self.set_border_type(border_type);
self
}
pub fn set_border_type(&mut self, border_type: BorderType) {
self.border_type = border_type;
}
pub fn border_style(mut self, border_style: Style) -> Self {
self.set_border_style(border_style);
self
}
pub fn set_border_style(&mut self, border_style: Style) {
self.border_style = border_style;
}
}
impl Widget for Grid {
fn render(self, area: Rect, buf: &mut tui::buffer::Buffer) {
let widths = self.widths(area.width).unwrap();
let heights = self.heights(area.height).unwrap();
let vertical_lines = Self::lines(area.x, &widths);
let horizontal_lines = Self::lines(area.y, &heights);
let top = horizontal_lines.first().unwrap();
let bottom = horizontal_lines.last().unwrap();
let left = vertical_lines.first().unwrap();
let right = vertical_lines.last().unwrap();
let set = BorderType::line_symbols(self.border_type);
for x in vertical_lines.iter() {
for y in *top..*bottom + 1 {
if !horizontal_lines.contains(&y) {
buf.set_string(*x, y, set.vertical, self.border_style);
}
}
}
for y in horizontal_lines.iter() {
for x in *left..*right + 1 {
if vertical_lines.contains(&x) {
buf.set_string(
x,
*y,
Self::from_pos(&x, y, left, right, top, bottom, &set),
self.border_style,
);
} else {
buf.set_string(x, *y, set.horizontal, self.border_style);
}
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum GridError {
NotEnoughLength,
ZeroLength,
}
impl Display for GridError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:?}", self))
}
}
impl Error for GridError {}