use crate::element::{Component, Element};
use crate::style::{Color, Modifier, Style};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DividerStyle {
#[default]
Single,
Double,
Dashed,
Dotted,
Bold,
Ascii,
}
impl DividerStyle {
pub fn char(&self) -> char {
match self {
DividerStyle::Single => '─',
DividerStyle::Double => '═',
DividerStyle::Dashed => '┄',
DividerStyle::Dotted => '·',
DividerStyle::Bold => '━',
DividerStyle::Ascii => '-',
}
}
}
#[derive(Debug, Clone)]
pub struct DividerProps {
pub width: Option<usize>,
pub style: DividerStyle,
pub color: Option<Color>,
pub dim: bool,
pub label: Option<String>,
pub label_color: Option<Color>,
}
impl Default for DividerProps {
fn default() -> Self {
Self {
width: None,
style: DividerStyle::Single,
color: None,
dim: false,
label: None,
label_color: None,
}
}
}
impl DividerProps {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn width(mut self, width: usize) -> Self {
self.width = Some(width);
self
}
#[must_use]
pub fn line_style(mut self, style: DividerStyle) -> Self {
self.style = style;
self
}
#[must_use]
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
#[must_use]
pub fn dim(mut self) -> Self {
self.dim = true;
self
}
#[must_use]
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
#[must_use]
pub fn label_color(mut self, color: Color) -> Self {
self.label_color = Some(color);
self
}
pub fn render_string(&self) -> String {
let width = self.width.unwrap_or(20);
let ch = self.style.char();
if let Some(ref label) = self.label {
let label_len = label.chars().count();
let remaining = width.saturating_sub(label_len + 2); let left = remaining / 2;
let right = remaining - left;
format!(
"{} {} {}",
ch.to_string().repeat(left),
label,
ch.to_string().repeat(right)
)
} else {
ch.to_string().repeat(width)
}
}
}
pub struct Divider;
impl Component for Divider {
type Props = DividerProps;
fn render(props: &Self::Props) -> Element {
let content = props.render_string();
let mut style = Style::new();
if let Some(color) = props.color {
style = style.fg(color);
}
if props.dim {
style = style.add_modifier(Modifier::DIM);
}
Element::styled_text(&content, style)
}
}
pub fn divider(width: usize) -> String {
DividerProps::new().width(width).render_string()
}
pub fn divider_with_label(width: usize, label: &str) -> String {
DividerProps::new()
.width(width)
.label(label)
.render_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divider_default() {
let props = DividerProps::default();
let s = props.render_string();
assert_eq!(s.chars().count(), 20);
assert!(s.chars().all(|c| c == '─'));
}
#[test]
fn test_divider_width() {
let props = DividerProps::new().width(10);
let s = props.render_string();
assert_eq!(s, "──────────");
}
#[test]
fn test_divider_double() {
let props = DividerProps::new()
.width(5)
.line_style(DividerStyle::Double);
let s = props.render_string();
assert_eq!(s, "═════");
}
#[test]
fn test_divider_with_label() {
let props = DividerProps::new().width(20).label("Test");
let s = props.render_string();
assert!(s.contains("Test"));
assert!(s.contains("─"));
}
#[test]
fn test_divider_helper() {
let s = divider(10);
assert_eq!(s.chars().count(), 10);
}
#[test]
fn test_divider_with_label_helper() {
let s = divider_with_label(20, "Hello");
assert!(s.contains("Hello"));
}
}