i3status_rs/
themes.rs

1pub mod color;
2pub mod separator;
3pub mod xresources;
4
5use std::fmt;
6use std::ops::{Deref, DerefMut};
7
8use serde::{Deserialize, de};
9
10use crate::errors::*;
11use crate::util;
12use crate::widget::State;
13use color::Color;
14use separator::Separator;
15
16#[derive(Debug, Clone)]
17pub struct Theme(pub ThemeInner);
18
19impl Default for Theme {
20    fn default() -> Self {
21        ThemeUserConfig::default()
22            .try_into()
23            .unwrap_or_else(|_| Self(ThemeInner::default()))
24    }
25}
26
27impl Deref for Theme {
28    type Target = ThemeInner;
29    fn deref(&self) -> &Self::Target {
30        &self.0
31    }
32}
33impl DerefMut for Theme {
34    fn deref_mut(&mut self) -> &mut Self::Target {
35        &mut self.0
36    }
37}
38
39#[derive(Deserialize, Debug, Clone, Default)]
40#[serde(deny_unknown_fields, default)]
41pub struct ThemeInner {
42    pub idle_bg: Color,
43    pub idle_fg: Color,
44    pub info_bg: Color,
45    pub info_fg: Color,
46    pub good_bg: Color,
47    pub good_fg: Color,
48    pub warning_bg: Color,
49    pub warning_fg: Color,
50    pub critical_bg: Color,
51    pub critical_fg: Color,
52    pub separator: Separator,
53    pub separator_bg: Color,
54    pub separator_fg: Color,
55    pub alternating_tint_bg: Color,
56    pub alternating_tint_fg: Color,
57    pub end_separator: Separator,
58    pub start_separator: Separator,
59}
60
61impl Theme {
62    pub fn get_colors(&self, state: State) -> (Color, Color) {
63        match state {
64            State::Idle => (self.idle_bg, self.idle_fg),
65            State::Info => (self.info_bg, self.info_fg),
66            State::Good => (self.good_bg, self.good_fg),
67            State::Warning => (self.warning_bg, self.warning_fg),
68            State::Critical => (self.critical_bg, self.critical_fg),
69        }
70    }
71
72    pub fn apply_overrides(&mut self, overrides: ThemeOverrides) -> Result<()> {
73        let copy = self.clone();
74
75        if let Some(separator) = overrides.separator {
76            self.separator = separator;
77        }
78        if let Some(end_separator) = overrides.end_separator {
79            self.end_separator = end_separator;
80        }
81        if let Some(start_separator) = overrides.start_separator {
82            self.start_separator = start_separator;
83        }
84
85        macro_rules! apply {
86            ($prop:tt) => {
87                if let Some(color) = overrides.$prop {
88                    self.$prop = color.eval(&copy)?;
89                }
90            };
91        }
92        apply!(idle_bg);
93        apply!(idle_fg);
94        apply!(info_bg);
95        apply!(info_fg);
96        apply!(good_bg);
97        apply!(good_fg);
98        apply!(warning_bg);
99        apply!(warning_fg);
100        apply!(critical_bg);
101        apply!(critical_fg);
102        apply!(separator_bg);
103        apply!(separator_fg);
104        apply!(alternating_tint_bg);
105        apply!(alternating_tint_fg);
106
107        Ok(())
108    }
109}
110
111#[derive(Deserialize, Default)]
112#[serde(deny_unknown_fields, default)]
113pub struct ThemeUserConfig {
114    pub theme: Option<String>,
115    pub overrides: Option<ThemeOverrides>,
116}
117
118#[derive(Deserialize, Debug, Clone, Default)]
119pub struct ThemeOverrides {
120    pub idle_bg: Option<ColorOrLink>,
121    pub idle_fg: Option<ColorOrLink>,
122    pub info_bg: Option<ColorOrLink>,
123    pub info_fg: Option<ColorOrLink>,
124    pub good_bg: Option<ColorOrLink>,
125    pub good_fg: Option<ColorOrLink>,
126    pub warning_bg: Option<ColorOrLink>,
127    pub warning_fg: Option<ColorOrLink>,
128    pub critical_bg: Option<ColorOrLink>,
129    pub critical_fg: Option<ColorOrLink>,
130    pub separator: Option<Separator>,
131    pub separator_bg: Option<ColorOrLink>,
132    pub separator_fg: Option<ColorOrLink>,
133    pub alternating_tint_bg: Option<ColorOrLink>,
134    pub alternating_tint_fg: Option<ColorOrLink>,
135    pub end_separator: Option<Separator>,
136    pub start_separator: Option<Separator>,
137}
138
139impl TryFrom<ThemeUserConfig> for Theme {
140    type Error = Error;
141
142    fn try_from(user_config: ThemeUserConfig) -> Result<Self, Self::Error> {
143        let name = user_config.theme.as_deref().unwrap_or("plain");
144        let file = util::find_file(name, Some("themes"), Some("toml"))
145            .or_error(|| format!("Theme '{name}' not found"))?;
146        let theme: ThemeInner = util::deserialize_toml_file(file)?;
147        let mut theme = Theme(theme);
148        if let Some(overrides) = user_config.overrides {
149            theme.apply_overrides(overrides)?;
150        }
151        Ok(theme)
152    }
153}
154
155#[derive(Debug, Clone)]
156pub enum ColorOrLink {
157    Color(Color),
158    Link { link: String },
159}
160
161impl ColorOrLink {
162    fn eval(self, theme: &Theme) -> Result<Color> {
163        Ok(match self {
164            Self::Color(c) => c,
165            Self::Link { link } => match link.as_str() {
166                "idle_bg" => theme.idle_bg,
167                "idle_fg" => theme.idle_fg,
168                "info_bg" => theme.info_bg,
169                "info_fg" => theme.info_fg,
170                "good_bg" => theme.good_bg,
171                "good_fg" => theme.good_fg,
172                "warning_bg" => theme.warning_bg,
173                "warning_fg" => theme.warning_fg,
174                "critical_bg" => theme.critical_bg,
175                "critical_fg" => theme.critical_fg,
176                "separator_bg" => theme.separator_bg,
177                "separator_fg" => theme.separator_fg,
178                "alternating_tint_bg" => theme.alternating_tint_bg,
179                "alternating_tint_fg" => theme.alternating_tint_fg,
180                _ => return Err(Error::new(format!("{link} is not a correct theme color"))),
181            },
182        })
183    }
184}
185
186impl<'de> Deserialize<'de> for ColorOrLink {
187    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
188    where
189        D: de::Deserializer<'de>,
190    {
191        struct Visitor;
192        impl<'de> de::Visitor<'de> for Visitor {
193            type Value = ColorOrLink;
194
195            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
196                formatter.write_str("color or link")
197            }
198
199            fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
200            where
201                A: de::MapAccess<'de>,
202            {
203                #[derive(Deserialize)]
204                #[serde(deny_unknown_fields)]
205                struct Link {
206                    link: String,
207                }
208                Link::deserialize(de::value::MapAccessDeserializer::new(map))
209                    .map(|link| ColorOrLink::Link { link: link.link })
210            }
211
212            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
213            where
214                E: de::Error,
215            {
216                v.parse::<Color>().serde_error().map(ColorOrLink::Color)
217            }
218        }
219        deserializer.deserialize_any(Visitor)
220    }
221}