use std::{
cmp::max,
hash::{DefaultHasher, Hash, Hasher},
marker::PhantomData,
};
use crate::{
borders,
buffer::Buffer,
enums::{Border, BorderType, Color},
geometry::Padding,
prelude::{Constraint, Direction, Rect, Vec2},
style::Style,
text::Text,
widgets::{Element, Layout, LayoutNode, Spacer, Span, Widget},
};
#[derive(Debug)]
pub struct Block<M: 'static = (), W = Element<M>> {
title: Box<dyn Text>,
borders: Border,
border_type: BorderType,
border_style: Style,
child: Element<M>,
child_type: PhantomData<W>,
}
impl<M> Block<M, Element<M>> {
#[must_use]
pub fn new<T>(child: T) -> Self
where
T: Into<Element<M>>,
{
Self {
title: Box::new(Span::new("")),
borders: Border::ALL,
border_type: BorderType::Normal,
border_style: Default::default(),
child: child.into(),
child_type: PhantomData,
}
}
}
impl<M, W> Block<M, W> {
#[must_use]
pub fn title<T>(mut self, title: T) -> Self
where
T: Into<Box<dyn Text>>,
{
self.title = title.into();
self
}
#[must_use]
pub fn borders(mut self, borders: Border) -> Self {
self.borders = borders;
self
}
#[must_use]
pub fn border_type(mut self, border_type: BorderType) -> Self {
self.border_type = border_type;
self
}
#[must_use]
pub fn border_style<T>(mut self, style: T) -> Self
where
T: Into<Style>,
{
self.border_style = style.into();
self
}
#[must_use]
pub fn border_color(mut self, color: Color) -> Self {
self.border_style = self.border_style.fg(color);
self
}
}
impl<M: Clone + 'static> Block<M, Spacer> {
#[must_use]
pub fn empty() -> Self {
Self {
title: Box::new(Span::new("")),
borders: Border::ALL,
border_type: BorderType::Normal,
border_style: Default::default(),
child: Spacer::new().into(),
child_type: PhantomData,
}
}
}
impl<M: Clone + 'static> Block<M, Layout<M>> {
#[must_use]
pub fn vertical() -> Self {
Self {
title: Box::new(Span::new("")),
borders: Border::ALL,
border_type: Default::default(),
border_style: Default::default(),
child: Layout::vertical().into(),
child_type: PhantomData,
}
}
#[must_use]
pub fn horizontal() -> Self {
Self {
title: Box::new(Span::new("")),
borders: Border::ALL,
border_type: Default::default(),
border_style: Default::default(),
child: Layout::horizontal().into(),
child_type: PhantomData,
}
}
#[must_use]
pub fn direction(mut self, direction: Direction) -> Self {
self.child =
self.child.map::<Layout<M>, _>(|l| l.direction(direction));
self
}
#[must_use]
pub fn style<T>(mut self, style: T) -> Self
where
T: Into<Style>,
{
self.child = self.child.map::<Layout<M>, _>(|l| l.style(style));
self
}
#[must_use]
pub fn bg<T>(mut self, bg: T) -> Self
where
T: Into<Option<Color>>,
{
self.child = self.child.map::<Layout<M>, _>(|l| l.bg(bg));
self
}
#[must_use]
pub fn fg<T>(mut self, fg: T) -> Self
where
T: Into<Option<Color>>,
{
self.child = self.child.map::<Layout<M>, _>(|l| l.fg(fg));
self
}
#[must_use]
pub fn padding<T>(mut self, padding: T) -> Self
where
T: Into<Padding>,
{
self.child = self.child.map::<Layout<M>, _>(|l| l.padding(padding));
self
}
#[must_use]
pub fn center(mut self) -> Self {
self.child = self.child.map::<Layout<M>, _>(|l| l.center());
self
}
pub fn push<T, C>(&mut self, child: T, constraint: C)
where
T: Into<Element<M>>,
C: Into<Constraint>,
{
if let Some(layout) = self.child.downcast_mut::<Layout<M>>() {
layout.push(child, constraint);
}
}
}
impl<M, W> Widget<M> for Block<M, W>
where
M: Clone + 'static,
W: Widget<M>,
{
fn render(&self, buffer: &mut Buffer, layout: &LayoutNode) {
let rect = layout.area;
let (_, r, _, l) = self.render_border(buffer, &rect);
let pos = Vec2::new(rect.x() + l, rect.y());
let size = Vec2::new(rect.width().saturating_sub(l + r), 1);
let trect = Rect::from_coords(pos, size);
_ = self.title.render_offset(buffer, trect, 0, None);
self.child.render(buffer, &layout.children[0]);
}
fn height(&self, size: &Vec2) -> usize {
let (width, height) = self.border_size();
let size = Vec2::new(
size.x.saturating_sub(width),
size.y.saturating_sub(height),
);
height + self.child.height(&size)
}
fn width(&self, size: &Vec2) -> usize {
let (width, height) = self.border_size();
let size = Vec2::new(
size.x.saturating_sub(width),
size.y.saturating_sub(height),
);
max(self.child.width(&size), self.title.get_text().len()) + width
}
fn children(&self) -> Vec<&Element<M>> {
vec![&self.child]
}
fn layout_hash(&self) -> u64 {
let mut hasher = DefaultHasher::new();
self.title.get_text().hash(&mut hasher);
self.borders.hash(&mut hasher);
hasher.finish()
}
fn layout(&self, node: &mut LayoutNode, area: Rect) {
node.children[0].layout(&self.child, area.inner(self.borders));
}
}
impl<M, W> Block<M, W> {
fn render_border(
&self,
buffer: &mut Buffer,
rect: &Rect,
) -> (usize, usize, usize, usize) {
let l = self.ver_border(buffer, rect, rect.left(), Border::LEFT);
let r = self.ver_border(buffer, rect, rect.right(), Border::RIGHT);
let t = self.hor_border(buffer, rect, rect.top(), Border::TOP);
let b = self.hor_border(buffer, rect, rect.bottom(), Border::BOTTOM);
if rect.width() <= 1 || rect.height() <= 1 {
return (t, r, b, l);
}
self.render_corner(buffer, *rect.pos(), borders!(TOP, LEFT));
self.render_corner(buffer, rect.top_right(), borders!(TOP, RIGHT));
self.render_corner(buffer, rect.bottom_left(), borders!(BOTTOM, LEFT));
self.render_corner(
buffer,
rect.bottom_right(),
borders!(BOTTOM, RIGHT),
);
(t, r, b, l)
}
fn hor_border(
&self,
buffer: &mut Buffer,
rect: &Rect,
y: usize,
border: Border,
) -> usize {
if (self.borders & border) == Border::NONE {
return 0;
}
let c = self.border_type.get(border);
let mut pos = Vec2::new(rect.x(), y);
while pos.x <= rect.right() {
buffer[pos].char(c).style(self.border_style);
pos.x += 1;
}
1
}
fn ver_border(
&self,
buffer: &mut Buffer,
rect: &Rect,
x: usize,
border: Border,
) -> usize {
if (self.borders & border) == Border::NONE {
return 0;
}
let c = self.border_type.get(border);
let mut pos = Vec2::new(x, rect.y());
while pos.y <= rect.bottom() {
buffer[pos].char(c).style(self.border_style);
pos.y += 1;
}
1
}
fn render_corner(&self, buffer: &mut Buffer, pos: Vec2, border: Border) {
if (self.borders & border) == border {
let c = self.border_type.get(border);
buffer[pos].char(c).style(self.border_style);
}
}
fn border_size(&self) -> (usize, usize) {
(self.hor_border_size(), self.ver_border_size())
}
fn hor_border_size(&self) -> usize {
(self.borders & Border::RIGHT != Border::NONE) as usize
+ (self.borders & Border::LEFT != Border::NONE) as usize
}
fn ver_border_size(&self) -> usize {
(self.borders & Border::TOP != Border::NONE) as usize
+ (self.borders & Border::BOTTOM != Border::NONE) as usize
}
}
impl<M, W> From<Block<M, W>> for Box<dyn Widget<M>>
where
M: Clone + 'static,
W: Widget<M> + 'static,
{
fn from(value: Block<M, W>) -> Self {
Box::new(value)
}
}
impl<M, W> From<Block<M, W>> for Element<M>
where
M: Clone + 'static,
W: Widget<M> + 'static,
{
fn from(value: Block<M, W>) -> Self {
Element::new(value)
}
}