use serde::{de::Visitor, Deserialize, Serialize}; use std::ops::Deref; use termion::color; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Theme { pub windows: Colors, pub frames: Frames, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct ColorSet { pub fg: Color, pub bg: Color, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Frames { pub outer: Frame, pub inner: Frame, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct FrameChars { pub top: char, pub bottom: char, pub left: char, pub right: char, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Frame { pub width: u16, pub frame_chars: FrameChars, pub color: ColorSet, } impl Default for ColorSet { fn default() -> Self { Self { fg: "#ffffff".try_into().unwrap(), bg: "#000000".try_into().unwrap(), } } } impl ToString for ColorSet { #[inline(always)] fn to_string(&self) -> String { self.bg.bg_string() + &self.fg.fg_string() } } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Colors { pub primary: ColorSet, pub subwin: ColorSet, pub highlight: ColorSet, } #[derive(Clone, Copy, Debug)] pub struct Color(color::Rgb); impl Serialize for Color { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let rgb = self.0; serializer.serialize_str(&format!( "#{:02X}{:02X}{:02X}", rgb.0, rgb.1, rgb.2, )) } } impl<'de> Deserialize<'de> for Color { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct ColorCodeVisitor; impl<'de> Visitor<'de> for ColorCodeVisitor { fn expecting( &self, formatter: &mut std::fmt::Formatter, ) -> std::fmt::Result { formatter.write_str("hex color code") } type Value = Color; fn visit_str(self, v: &str) -> Result where E: serde::de::Error, { match TryInto::::try_into(v) { Ok(c) => Ok(c), Err(e) => Err(E::custom(e.to_string())), } } fn visit_string( self, v: String, ) -> Result where E: serde::de::Error, { match TryInto::::try_into(v) { Ok(c) => Ok(c), Err(e) => Err(E::custom(e.to_string())), } } } deserializer.deserialize_any(ColorCodeVisitor {}) } } impl Deref for Color { type Target = color::Rgb; fn deref(&self) -> &Self::Target { &self.0 } } impl TryFrom for Color { type Error = anyhow::Error; fn try_from(value: String) -> Result { Ok(value.as_str().try_into()?) } } impl TryFrom<&str> for Color { type Error = anyhow::Error; fn try_from(value: &str) -> Result { if value.len() < 6 || value.len() > 7 { panic!("hex code length invalid: {}", value.len()); } let mut i = 0; if value.starts_with('#') { i = 1; } Ok(Self(color::Rgb( u8::from_str_radix(&value[i..i + 2], 16) .map_err(|_| anyhow::anyhow!("red hex"))?, u8::from_str_radix(&value[i + 2..i + 4], 16) .map_err(|_| anyhow::anyhow!("green hex"))?, u8::from_str_radix(&value[i + 4..i + 6], 16) .map_err(|_| anyhow::anyhow!("blue hex"))?, ))) } } impl Default for Theme { fn default() -> Self { Self { windows: Colors { primary: ColorSet { fg: "#330033".try_into().unwrap(), bg: "#a006d3".try_into().unwrap(), }, highlight: ColorSet { fg: "#ffffff".try_into().unwrap(), bg: "#0000ff".try_into().unwrap(), }, subwin: ColorSet { fg: "#ffffff".try_into().unwrap(), bg: "#500170".try_into().unwrap(), }, }, frames: Frames { outer: Frame { color: ColorSet { fg: "#ff36ff".try_into().unwrap(), bg: "#9005c2".try_into().unwrap(), }, frame_chars: FrameChars { top: ' ', bottom: ' ', left: ' ', right: ' ', }, width: 1, }, inner: Frame { color: ColorSet { fg: "#ffe6ff".try_into().unwrap(), bg: "#7003a0".try_into().unwrap(), }, frame_chars: FrameChars { top: ' ', bottom: ' ', left: ' ', right: ' ', }, width: 1, }, }, } } } impl Theme { #[inline(always)] pub fn primary(&self) -> String { self.windows.primary.to_string() } #[inline(always)] pub fn frame(&self) -> String { self.frames.outer.color.to_string() } }