use std::str::FromStr; use serde::{de::Unexpected, Deserialize, Serialize}; use super::{hex::ParseHex, parser::ParseError}; mod x11; pub use x11::X11Color; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Color { RGB { r: u8, g: u8, b: u8 }, X11(X11Color), } impl Serialize for Color { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(&self.to_string()) } } impl<'de> Deserialize<'de> for Color { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct ExpectedColor; impl serde::de::Expected for ExpectedColor { fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("an X11 color string or a hex color string") } } let str_val: String = Deserialize::deserialize(deserializer)?; Ok(Color::from_str(&str_val).map_err(|_| { serde::de::Error::invalid_value(Unexpected::Str(&str_val), &ExpectedColor) })?) } } impl Default for Color { fn default() -> Self { Self::RGB { r: Default::default(), g: Default::default(), b: Default::default(), } } } impl FromStr for Color { type Err = ParseError; fn from_str(s: &str) -> Result { if let Ok(x11) = X11Color::from_str(s) { return Ok(Self::X11(x11)); } Self::from_hex(s) } } impl Color { pub const BLACK: Color = Color::from_rgb(0, 0, 0); pub fn from_hex(hex: &str) -> Result { let expected_len = if hex.starts_with('#') { 7 } else { 6 }; if hex.len() != expected_len { return Err(ParseError::InvalidValue { value: hex.to_string(), expected: "a 6 digit hex value", }); } let hex = hex.strip_prefix('#').unwrap_or(hex); Ok(Self::RGB { r: (&hex[0..2]).parse_hex()?, g: (&hex[2..4]).parse_hex()?, b: (&hex[4..6]).parse_hex()?, }) } pub const fn from_rgb(r: u8, g: u8, b: u8) -> Self { Self::RGB { r, g, b } } pub const fn to_rgb(&self) -> (u8, u8, u8) { match self { Color::RGB { r, g, b } => (*r, *g, *b), Color::X11(xc) => xc.to_rgb(), } } } impl std::fmt::Display for Color { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Color::RGB { r, g, b } => write!(f, "#{r:02X}{g:02X}{b:02X}"), Color::X11(color) => f.write_str(&color.to_string()), } } } impl From for cnx::text::Color { fn from(value: Color) -> Self { let (r, g, b) = value.to_rgb(); cnx::text::Color::from_rgb(r, g, b) } } #[cfg(test)] mod test { use serde::{Deserialize, Serialize}; use super::{Color, X11Color}; #[derive(Debug, Serialize, Deserialize)] pub struct ColorContainer { color: Color, } fn color_serialize_deserialize(color: Color) { let original_color = color.clone(); let color_str = toml::to_string_pretty(&ColorContainer { color }).expect("serialization failure"); let color_parsed: ColorContainer = toml::from_str(color_str.as_str()).expect("deserialization failure"); assert_eq!(original_color, color_parsed.color); } #[test] fn color_serialize_deserialize_rgb() { color_serialize_deserialize(Color::RGB { r: 128, g: 128, b: 128, }); } #[test] fn color_serialize_deserialize_x11() { color_serialize_deserialize(Color::X11(X11Color::HotPink2)); } }