use serde::{ de::{Error, Visitor}, Deserialize, Deserializer, Serialize, Serializer, }; use std::{ collections::{BTreeMap, HashMap}, fmt, }; use crate::data::Palette; #[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct UiConfig { pub pane_frames: FrameConfig, } impl UiConfig { pub fn merge(&self, other: UiConfig) -> Self { let mut merged = self.clone(); merged.pane_frames = merged.pane_frames.merge(other.pane_frames); merged } } #[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct FrameConfig { pub rounded_corners: bool, pub hide_session_name: bool, } impl FrameConfig { pub fn merge(&self, other: FrameConfig) -> Self { let mut merged = self.clone(); merged.rounded_corners = other.rounded_corners; merged.hide_session_name = other.hide_session_name; merged } } #[derive(Clone, PartialEq, Default, Serialize, Deserialize)] pub struct Themes(HashMap); impl fmt::Debug for Themes { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut stable_sorted = BTreeMap::new(); for (theme_name, theme) in self.0.iter() { stable_sorted.insert(theme_name, theme); } write!(f, "{:#?}", stable_sorted) } } impl Themes { pub fn from_data(theme_data: HashMap) -> Self { Themes(theme_data) } pub fn insert(&mut self, theme_name: String, theme: Theme) { self.0.insert(theme_name, theme); } pub fn merge(&self, mut other: Themes) -> Self { let mut merged = self.clone(); for (name, theme) in other.0.drain() { merged.0.insert(name, theme); } merged } pub fn get_theme(&self, theme_name: &str) -> Option<&Theme> { self.0.get(theme_name) } pub fn inner(&self) -> &HashMap { &self.0 } } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Theme { #[serde(flatten)] pub palette: Palette, pub sourced_from_external_file: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct HexColor(u8, u8, u8); impl From for (u8, u8, u8) { fn from(e: HexColor) -> (u8, u8, u8) { let HexColor(r, g, b) = e; (r, g, b) } } pub struct HexColorVisitor(); impl<'de> Visitor<'de> for HexColorVisitor { type Value = HexColor; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "a hex color in the format #RGB or #RRGGBB") } fn visit_str(self, s: &str) -> Result where E: Error, { if let Some(stripped) = s.strip_prefix('#') { return self.visit_str(stripped); } if s.len() == 3 { Ok(HexColor( u8::from_str_radix(&s[0..1], 16).map_err(E::custom)? * 0x11, u8::from_str_radix(&s[1..2], 16).map_err(E::custom)? * 0x11, u8::from_str_radix(&s[2..3], 16).map_err(E::custom)? * 0x11, )) } else if s.len() == 6 { Ok(HexColor( u8::from_str_radix(&s[0..2], 16).map_err(E::custom)?, u8::from_str_radix(&s[2..4], 16).map_err(E::custom)?, u8::from_str_radix(&s[4..6], 16).map_err(E::custom)?, )) } else { Err(Error::custom( "Hex color must be of form \"#RGB\" or \"#RRGGBB\"", )) } } } impl<'de> Deserialize<'de> for HexColor { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_str(HexColorVisitor()) } } impl Serialize for HexColor { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(format!("{:02X}{:02X}{:02X}", self.0, self.1, self.2).as_str()) } } #[cfg(test)] #[path = "./unit/theme_test.rs"] mod theme_test;