use std::fmt; use std::ops::MulAssign; use std::str::FromStr; use std::{convert::TryFrom, ops::Mul}; use serde::{Deserialize, Serialize}; /// `TextColor` describes the color of the text #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum Color { White, Black, Red, Blue, Green, Orange, Yellow, Custom(u8, u8, u8), } impl Color { pub fn r(&self) -> u8 { match self { Color::White => 255, Color::Black => 0, Color::Red => 255, Color::Blue => 0, Color::Green => 0, Color::Orange => 255, Color::Yellow => 255, Color::Custom(r, _g, _b) => *r, } } pub fn g(&self) -> u8 { match self { Color::White => 255, Color::Black => 0, Color::Red => 0, Color::Blue => 0, Color::Green => 255, Color::Orange => 166, Color::Yellow => 255, Color::Custom(_r, g, _b) => *g, } } pub fn b(&self) -> u8 { match self { Color::White => 255, Color::Black => 0, Color::Red => 0, Color::Blue => 255, Color::Green => 0, Color::Orange => 0, Color::Yellow => 0, Color::Custom(_r, _g, b) => *b, } } } impl Into<[f32; 3]> for Color { fn into(self) -> [f32; 3] { match self { Color::White => [1.0, 1.0, 1.0], Color::Black => [0.0, 0.0, 0.0], Color::Red => [1.0, 0.0, 0.0], Color::Blue => [0.0, 0.0, 1.0], Color::Green => [0.0, 1.0, 0.0], Color::Orange => [1.0, 0.65, 0.0], Color::Yellow => [1.0, 1.0, 0.0], Color::Custom(r, g, b) => [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0], } } } impl Into<[u8; 3]> for Color { fn into(self) -> [u8; 3] { match self { Color::White => [255, 255, 255], Color::Black => [0, 0, 0], Color::Red => [255, 0, 0], Color::Blue => [0, 0, 255], Color::Green => [0, 255, 0], Color::Orange => [255, 166, 0], Color::Yellow => [255, 255, 0], Color::Custom(r, g, b) => [r, g, b], } } } impl Mul for Color { type Output = Self; fn mul(self, rhs: f32) -> Self::Output { let values: [f32; 3] = self.into(); let r = (values[0] * rhs).min(1.0); let g = (values[1] * rhs).min(1.0); let b = (values[2] * rhs).min(1.0); Color::from([r, g, b]) } } impl MulAssign for Color { fn mul_assign(&mut self, rhs: f32) { *self = *self * rhs; } } impl From<[u8; 3]> for Color { fn from(array: [u8; 3]) -> Self { Self::Custom(array[0], array[1], array[2]) } } impl From<[f32; 3]> for Color { fn from(array: [f32; 3]) -> Self { Self::Custom( (array[0] * 255.0).min(255.0) as u8, (array[1] * 255.0).min(255.0) as u8, (array[2] * 255.0).min(255.0) as u8, ) } } impl TryFrom<&str> for Color { type Error = anyhow::Error; fn try_from(text: &str) -> Result { // check if color is a hex value if text.starts_with("#") { let without_prefix = text.trim_start_matches("#"); if without_prefix.len() != 6 { return Err(anyhow::Error::msg(format!( "Hex color format has wrong format: {}", without_prefix ))); } let r = u8::from_str_radix(&without_prefix[0..2], 16)?; let g = u8::from_str_radix(&without_prefix[2..4], 16)?; let b = u8::from_str_radix(&without_prefix[4..6], 16)?; return Ok(Color::Custom(r, g, b)); } match text { "white" => Ok(Color::White), "black" => Ok(Color::Black), "red" => Ok(Color::Red), "blue" => Ok(Color::Blue), "green" => Ok(Color::Green), "orange" => Ok(Color::Orange), "yellow" => Ok(Color::Yellow), _ => Err(anyhow::Error::msg(format!("Unknown color: {}", text))), } } } impl fmt::Display for Color { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Color::White => write!(f, "white"), Color::Black => write!(f, "black"), Color::Red => write!(f, "red"), Color::Blue => write!(f, "blue"), Color::Green => write!(f, "green"), Color::Orange => write!(f, "orange"), Color::Yellow => write!(f, "yellow"), Color::Custom(r, g, b) => write!(f, "#{:X?}{:X?}{:X?}", r, g, b), } } } impl FromStr for Color { type Err = anyhow::Error; fn from_str(s: &str) -> Result { Self::try_from(s) } } impl Default for Color { fn default() -> Self { Self::Black } } #[test] fn simple_color_conversion() { let color = Color::try_from("white").unwrap(); assert_eq!(color, Color::White); } #[test] fn hex_color_conversion() { let color = Color::try_from("#FFFFFF").unwrap(); assert_eq!(color, Color::Custom(255, 255, 255)); let string = format!("{}", color); assert_eq!(string, "#FFFFFF"); }