use alloc::fmt;
use bitflags::bitflags;
use ratatui_core::symbols::border;
use strum::{Display, EnumString};
bitflags! {
#[derive(Default, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Borders: u8 {
const TOP = 0b0001;
const RIGHT = 0b0010;
const BOTTOM = 0b0100;
const LEFT = 0b1000;
const ALL = Self::TOP.bits() | Self::RIGHT.bits() | Self::BOTTOM.bits() | Self::LEFT.bits();
}
}
impl Borders {
pub const NONE: Self = Self::empty();
}
#[derive(Debug, Default, Display, EnumString, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum BorderType {
#[default]
Plain,
Rounded,
Double,
Thick,
LightDoubleDashed,
HeavyDoubleDashed,
LightTripleDashed,
HeavyTripleDashed,
LightQuadrupleDashed,
HeavyQuadrupleDashed,
QuadrantInside,
QuadrantOutside,
}
impl BorderType {
pub const fn border_symbols<'a>(border_type: Self) -> border::Set<'a> {
match border_type {
Self::Plain => border::PLAIN,
Self::Rounded => border::ROUNDED,
Self::Double => border::DOUBLE,
Self::Thick => border::THICK,
Self::LightDoubleDashed => border::LIGHT_DOUBLE_DASHED,
Self::HeavyDoubleDashed => border::HEAVY_DOUBLE_DASHED,
Self::LightTripleDashed => border::LIGHT_TRIPLE_DASHED,
Self::HeavyTripleDashed => border::HEAVY_TRIPLE_DASHED,
Self::LightQuadrupleDashed => border::LIGHT_QUADRUPLE_DASHED,
Self::HeavyQuadrupleDashed => border::HEAVY_QUADRUPLE_DASHED,
Self::QuadrantInside => border::QUADRANT_INSIDE,
Self::QuadrantOutside => border::QUADRANT_OUTSIDE,
}
}
pub const fn to_border_set<'a>(self) -> border::Set<'a> {
Self::border_symbols(self)
}
}
impl fmt::Debug for Borders {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
return write!(f, "NONE");
}
if self.is_all() {
return write!(f, "ALL");
}
let mut names = self.iter_names().map(|(name, _)| name);
if let Some(first) = names.next() {
write!(f, "{first}")?;
}
for name in names {
write!(f, " | {name}")?;
}
Ok(())
}
}
#[macro_export]
macro_rules! border {
() => {
$crate::borders::Borders::NONE
};
($b:ident) => {
$crate::borders::Borders::$b
};
($first:ident,$($other:ident),*) => {
$crate::borders::Borders::$first
$(
.union($crate::borders::Borders::$other)
)*
};
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn test_borders_debug() {
assert_eq!(format!("{:?}", Borders::empty()), "NONE");
assert_eq!(format!("{:?}", Borders::NONE), "NONE");
assert_eq!(format!("{:?}", Borders::TOP), "TOP");
assert_eq!(format!("{:?}", Borders::BOTTOM), "BOTTOM");
assert_eq!(format!("{:?}", Borders::LEFT), "LEFT");
assert_eq!(format!("{:?}", Borders::RIGHT), "RIGHT");
assert_eq!(format!("{:?}", Borders::ALL), "ALL");
assert_eq!(format!("{:?}", Borders::all()), "ALL");
assert_eq!(
format!("{:?}", Borders::TOP | Borders::BOTTOM),
"TOP | BOTTOM"
);
}
#[test]
fn can_be_const() {
const NOTHING: Borders = border!();
const JUST_TOP: Borders = border!(TOP);
const TOP_BOTTOM: Borders = border!(TOP, BOTTOM);
const RIGHT_OPEN: Borders = border!(TOP, LEFT, BOTTOM);
assert_eq!(NOTHING, Borders::NONE);
assert_eq!(JUST_TOP, Borders::TOP);
assert_eq!(TOP_BOTTOM, Borders::TOP | Borders::BOTTOM);
assert_eq!(RIGHT_OPEN, Borders::TOP | Borders::LEFT | Borders::BOTTOM);
}
#[test]
fn border_empty() {
let empty = Borders::NONE;
assert_eq!(empty, border!());
}
#[test]
fn border_all() {
let all = Borders::ALL;
assert_eq!(all, border!(ALL));
assert_eq!(all, border!(TOP, BOTTOM, LEFT, RIGHT));
}
#[test]
fn border_left_right() {
let left_right = Borders::from_bits(Borders::LEFT.bits() | Borders::RIGHT.bits());
assert_eq!(left_right, Some(border!(RIGHT, LEFT)));
}
}