wireman_theme/
skin.rs

1use std::{collections::HashMap, error::Error, str::FromStr};
2
3use serde::Deserialize;
4
5use crate::{color::Color, set_fg_bg, theme::LineNumbers, Theme};
6
7#[derive(Debug, Deserialize)]
8pub struct Skin {
9    #[serde(default)]
10    pub colors: HashMap<String, Color>,
11    #[serde(default)]
12    pub base: Base,
13    #[serde(default)]
14    pub title: Title,
15    #[serde(default)]
16    pub highlight: Highlight,
17    #[serde(default)]
18    pub border: Border,
19    #[serde(default)]
20    pub footer: Footer,
21    #[serde(default)]
22    pub status: Status,
23    #[serde(default)]
24    pub editor: Editor,
25}
26
27impl Default for Skin {
28    fn default() -> Self {
29        toml::from_str(include_str!("../assets/default.toml")).unwrap()
30    }
31}
32
33impl Skin {
34    pub(crate) fn from_file(file_path: &str) -> Result<Self, Box<dyn Error>> {
35        let f = shellexpand::env(file_path).map_or(file_path.to_string(), |x| x.to_string());
36
37        let toml_content = std::fs::read_to_string(f)?;
38
39        Ok(toml::from_str(&toml_content)?)
40    }
41
42    #[allow(clippy::too_many_lines)]
43    pub(crate) fn apply_to(&self, theme: &mut Theme) {
44        // Base
45        if let Some(target) = &self.base.focused {
46            set_fg_bg!(theme.base.focused, target, self.colors);
47        }
48        if let Some(target) = &self.base.unfocused {
49            set_fg_bg!(theme.base.unfocused, target, self.colors);
50        }
51
52        // Highlight
53        if let Some(target) = &self.highlight.focused {
54            set_fg_bg!(theme.highlight.focused, target, self.colors);
55        }
56        if let Some(target) = &self.highlight.unfocused {
57            set_fg_bg!(theme.highlight.unfocused, target, self.colors);
58        }
59
60        // Border
61        if let Some(target) = &self.border.unfocused {
62            set_fg_bg!(theme.border.unfocused, target, self.colors);
63        }
64        if let Some(target) = &self.border.focused {
65            set_fg_bg!(theme.border.focused, target, self.colors);
66        }
67
68        // Title
69        if let Some(target) = &self.title.focused {
70            set_fg_bg!(theme.title.focused, target, self.colors);
71        }
72        if let Some(target) = &self.title.unfocused {
73            set_fg_bg!(theme.title.unfocused, target, self.colors);
74        }
75
76        // Footer
77        if let Some(hide_footer) = self.footer.hide {
78            theme.hide_footer = hide_footer;
79        } else {
80            theme.hide_footer = false;
81        }
82
83        // Status
84        if let Some(hide_status) = self.status.hide {
85            theme.hide_status = hide_status;
86        } else {
87            theme.hide_status = false;
88        }
89
90        // Editor
91        if let Some(line_numbers) = self.editor.line_numbers {
92            theme.editor.line_numbers = line_numbers;
93        }
94    }
95}
96
97#[derive(Default, Debug, Deserialize)]
98pub(crate) struct Base {
99    pub focused: Option<FgBg>,
100    pub unfocused: Option<FgBg>,
101}
102
103#[derive(Default, Debug, Deserialize)]
104pub(crate) struct Highlight {
105    pub focused: Option<FgBg>,
106    pub unfocused: Option<FgBg>,
107}
108
109#[derive(Default, Debug, Deserialize)]
110pub(crate) struct Title {
111    pub focused: Option<FgBg>,
112    pub unfocused: Option<FgBg>,
113}
114
115#[derive(Debug, Deserialize, Default)]
116pub(crate) struct Border {
117    pub focused: Option<FgBg>,
118    pub unfocused: Option<FgBg>,
119}
120
121#[derive(Debug, Deserialize, Default)]
122pub(crate) struct Footer {
123    hide: Option<bool>,
124}
125
126#[derive(Debug, Deserialize, Default)]
127pub(crate) struct Status {
128    hide: Option<bool>,
129}
130
131#[derive(Debug, Deserialize, Default)]
132pub(crate) struct Editor {
133    pub line_numbers: Option<LineNumbers>,
134}
135
136#[derive(Debug, Deserialize, Default)]
137pub(crate) struct FgBg {
138    pub foreground: Option<String>,
139    pub background: Option<String>,
140}
141
142pub(crate) fn resolve_color(colors: &HashMap<String, Color>, color: Option<&str>) -> Option<Color> {
143    let color = color?;
144
145    if let Some(color) = colors.get(color) {
146        return Some(*color);
147    }
148
149    Color::from_str(color).ok()
150}
151
152pub(crate) mod macros {
153    #[macro_export]
154    macro_rules! set_fg_bg {
155        ($theme:expr, $fg_bg:expr, $colors:expr) => {
156            let fc = resolve_color(&$colors, $fg_bg.foreground.as_deref());
157            let bc = resolve_color(&$colors, $fg_bg.background.as_deref());
158            if let Some(fc) = fc {
159                $theme = $theme.fg(fc.0);
160            }
161            if let Some(bc) = bc {
162                $theme = $theme.bg(bc.0);
163            }
164        };
165    }
166}