use crate::element::{Component, Element};
use crate::style::{Color, Modifier, Style};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextWrap {
#[default]
Wrap,
NoWrap,
Truncate,
TruncateStart,
TruncateMiddle,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct TextProps {
pub content: String,
pub color: Option<Color>,
pub bg_color: Option<Color>,
pub bold: bool,
pub dim: bool,
pub italic: bool,
pub underline: bool,
pub strikethrough: bool,
pub inverse: bool,
pub wrap: TextWrap,
}
impl TextProps {
pub fn new(content: impl Into<String>) -> Self {
Self {
content: content.into(),
..Default::default()
}
}
#[must_use]
pub fn color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
#[must_use]
pub fn bg_color(mut self, color: Color) -> Self {
self.bg_color = Some(color);
self
}
#[must_use]
pub fn bold(mut self) -> Self {
self.bold = true;
self
}
#[must_use]
pub fn dim(mut self) -> Self {
self.dim = true;
self
}
#[must_use]
pub fn italic(mut self) -> Self {
self.italic = true;
self
}
#[must_use]
pub fn underline(mut self) -> Self {
self.underline = true;
self
}
#[must_use]
pub fn strikethrough(mut self) -> Self {
self.strikethrough = true;
self
}
#[must_use]
pub fn inverse(mut self) -> Self {
self.inverse = true;
self
}
#[must_use]
pub fn wrap(mut self, wrap: TextWrap) -> Self {
self.wrap = wrap;
self
}
pub fn to_style(&self) -> Style {
let mut style = Style::new();
if let Some(color) = self.color {
style = style.fg(color);
}
if let Some(bg) = self.bg_color {
style = style.bg(bg);
}
if self.bold {
style = style.add_modifier(Modifier::BOLD);
}
if self.dim {
style = style.add_modifier(Modifier::DIM);
}
if self.italic {
style = style.add_modifier(Modifier::ITALIC);
}
if self.underline {
style = style.add_modifier(Modifier::UNDERLINED);
}
if self.strikethrough {
style = style.add_modifier(Modifier::CROSSED_OUT);
}
if self.inverse {
style = style.add_modifier(Modifier::REVERSED);
}
style
}
}
pub struct Text;
impl Component for Text {
type Props = TextProps;
fn render(props: &Self::Props) -> Element {
Element::styled_text(&props.content, props.to_style())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_props_new() {
let props = TextProps::new("Hello");
assert_eq!(props.content, "Hello");
assert!(props.color.is_none());
assert!(!props.bold);
}
#[test]
fn test_text_props_builder() {
let props = TextProps::new("Test")
.color(Color::Red)
.bg_color(Color::Blue)
.bold()
.italic()
.underline();
assert_eq!(props.content, "Test");
assert_eq!(props.color, Some(Color::Red));
assert_eq!(props.bg_color, Some(Color::Blue));
assert!(props.bold);
assert!(props.italic);
assert!(props.underline);
}
#[test]
fn test_text_props_to_style() {
let props = TextProps::new("Test").color(Color::Green).bold().italic();
let style = props.to_style();
assert_eq!(style.fg, Color::Green);
assert!(style.modifiers.contains(Modifier::BOLD));
assert!(style.modifiers.contains(Modifier::ITALIC));
}
#[test]
fn test_text_props_to_style_with_all_modifiers() {
let props = TextProps {
content: "Test".into(),
bold: true,
dim: true,
italic: true,
underline: true,
strikethrough: true,
inverse: true,
..Default::default()
};
let style = props.to_style();
assert!(style.modifiers.contains(Modifier::BOLD));
assert!(style.modifiers.contains(Modifier::DIM));
assert!(style.modifiers.contains(Modifier::ITALIC));
assert!(style.modifiers.contains(Modifier::UNDERLINED));
assert!(style.modifiers.contains(Modifier::CROSSED_OUT));
assert!(style.modifiers.contains(Modifier::REVERSED));
}
#[test]
fn test_text_render() {
let props = TextProps::new("Hello").color(Color::Red);
let elem = Text::render(&props);
match elem {
Element::Text { content, style } => {
assert_eq!(content, "Hello");
assert_eq!(style.fg, Color::Red);
}
_ => panic!("Expected Text element"),
}
}
#[test]
fn test_text_wrap_default() {
assert_eq!(TextWrap::default(), TextWrap::Wrap);
}
}