1use serde::{
2 de::{Error, Visitor},
3 Deserialize, Deserializer, Serialize, Serializer,
4};
5use std::{
6 collections::{BTreeMap, HashMap},
7 fmt,
8};
9
10use crate::data::Styling;
11
12#[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)]
13pub struct UiConfig {
14 pub pane_frames: FrameConfig,
15}
16
17impl UiConfig {
18 pub fn merge(&self, other: UiConfig) -> Self {
19 let mut merged = self.clone();
20 merged.pane_frames = merged.pane_frames.merge(other.pane_frames);
21 merged
22 }
23}
24
25#[derive(Debug, Default, Clone, Copy, PartialEq, Deserialize, Serialize)]
26pub struct FrameConfig {
27 pub rounded_corners: bool,
28 pub hide_session_name: bool,
29}
30
31impl FrameConfig {
32 pub fn merge(&self, other: FrameConfig) -> Self {
33 let mut merged = self.clone();
34 merged.rounded_corners = other.rounded_corners;
35 merged.hide_session_name = other.hide_session_name;
36 merged
37 }
38}
39
40#[derive(Clone, PartialEq, Default, Serialize, Deserialize)]
41pub struct Themes(HashMap<String, Theme>);
42
43impl fmt::Debug for Themes {
44 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
45 let mut stable_sorted = BTreeMap::new();
46 for (theme_name, theme) in self.0.iter() {
47 stable_sorted.insert(theme_name, theme);
48 }
49 write!(f, "{:#?}", stable_sorted)
50 }
51}
52
53impl Themes {
54 pub fn from_data(theme_data: HashMap<String, Theme>) -> Self {
55 Themes(theme_data)
56 }
57 pub fn insert(&mut self, theme_name: String, theme: Theme) {
58 self.0.insert(theme_name, theme);
59 }
60 pub fn merge(&self, mut other: Themes) -> Self {
61 let mut merged = self.clone();
62 for (name, theme) in other.0.drain() {
63 merged.0.insert(name, theme);
64 }
65 merged
66 }
67 pub fn get_theme(&self, theme_name: &str) -> Option<&Theme> {
68 self.0.get(theme_name)
69 }
70 pub fn inner(&self) -> &HashMap<String, Theme> {
71 &self.0
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
76pub struct Theme {
77 pub sourced_from_external_file: bool,
78 #[serde(flatten)]
79 pub palette: Styling,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
83pub struct HexColor(u8, u8, u8);
84
85impl From<HexColor> for (u8, u8, u8) {
86 fn from(e: HexColor) -> (u8, u8, u8) {
87 let HexColor(r, g, b) = e;
88 (r, g, b)
89 }
90}
91
92pub struct HexColorVisitor();
93
94impl<'de> Visitor<'de> for HexColorVisitor {
95 type Value = HexColor;
96
97 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
98 write!(formatter, "a hex color in the format #RGB or #RRGGBB")
99 }
100
101 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
102 where
103 E: Error,
104 {
105 if let Some(stripped) = s.strip_prefix('#') {
106 return self.visit_str(stripped);
107 }
108
109 if s.len() == 3 {
110 Ok(HexColor(
111 u8::from_str_radix(&s[0..1], 16).map_err(E::custom)? * 0x11,
112 u8::from_str_radix(&s[1..2], 16).map_err(E::custom)? * 0x11,
113 u8::from_str_radix(&s[2..3], 16).map_err(E::custom)? * 0x11,
114 ))
115 } else if s.len() == 6 {
116 Ok(HexColor(
117 u8::from_str_radix(&s[0..2], 16).map_err(E::custom)?,
118 u8::from_str_radix(&s[2..4], 16).map_err(E::custom)?,
119 u8::from_str_radix(&s[4..6], 16).map_err(E::custom)?,
120 ))
121 } else {
122 Err(Error::custom(
123 "Hex color must be of form \"#RGB\" or \"#RRGGBB\"",
124 ))
125 }
126 }
127}
128
129impl<'de> Deserialize<'de> for HexColor {
130 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131 where
132 D: Deserializer<'de>,
133 {
134 deserializer.deserialize_str(HexColorVisitor())
135 }
136}
137impl Serialize for HexColor {
138 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
139 where
140 S: Serializer,
141 {
142 serializer.serialize_str(format!("{:02X}{:02X}{:02X}", self.0, self.1, self.2).as_str())
143 }
144}
145
146#[cfg(test)]
147#[path = "./unit/theme_test.rs"]
148mod theme_test;