use std::{fmt, ops};
pub trait SendableComponent: fmt::Display {
fn as_any(&self) -> &dyn std::any::Any;
fn clone_box(&self) -> Box<dyn SendableComponent>;
}
impl Clone for Box<dyn SendableComponent> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[derive(Clone)]
pub struct TextComponent {
color: Option<u32>,
text: String,
parts: Vec<Box<dyn SendableComponent>>,
}
impl TextComponent {
pub fn color(mut self, color: u32) -> Self {
self.color = Some(color);
self
}
}
fn rgba_to_ansi(rgba: u32) -> String {
let r = (rgba >> 16) & 0xFF;
let g = (rgba >> 8) & 0xFF;
let b = rgba & 0xFF;
format!("38;2;{};{};{}", r, g, b)
}
impl fmt::Display for TextComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let write_parts = |f: &mut fmt::Formatter<'_>| -> fmt::Result {
if self.parts.is_empty() {
Ok(())
} else {
for part in &self.parts {
write!(f, "{}", part)?;
}
Ok(())
}
};
if let Some(color) = &self.color {
let ansi_color = rgba_to_ansi(*color);
write!(f, "\x1b[{}m", ansi_color)?;
write!(f, "{}", self.text)?;
write!(f, "\x1b[0m")?;
} else {
write!(f, "{}", self.text)?;
}
write_parts(f)
}
}
impl SendableComponent for TextComponent {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn clone_box(&self) -> Box<dyn SendableComponent> {
Box::new(self.clone())
}
}
#[allow(dead_code)]
pub struct Component {
parts: Vec<Box<dyn SendableComponent>>,
}
impl Component {
pub fn empty() -> TextComponent {
TextComponent {
color: None,
text: String::new(),
parts: Vec::new(),
}
}
pub fn text(text: &str) -> TextComponent {
TextComponent {
color: None,
text: text.to_string(),
parts: Vec::new(),
}
}
}
impl ops::Add<TextComponent> for TextComponent {
type Output = TextComponent;
fn add(mut self, other: TextComponent) -> Self::Output {
self.parts.push(Box::new(TextComponent {
color: other.color,
text: other.text,
parts: Vec::new(),
}));
for comp in other.parts {
self.parts.push(comp);
}
self
}
}