wireman_theme/
skin.rs

1use std::{collections::HashMap, error::Error, str::FromStr};
2
3use ratatui::widgets::BorderType;
4use serde::Deserialize;
5
6use crate::{color::Color, set_fg_bg, theme::LineNumbers, Theme};
7
8#[derive(Debug, Deserialize)]
9pub struct Skin {
10    #[serde(default)]
11    pub colors: HashMap<String, Color>,
12    #[serde(default)]
13    pub base: Base,
14    #[serde(default)]
15    pub title: Title,
16    #[serde(default)]
17    pub highlight: Highlight,
18    #[serde(default)]
19    pub border: Border,
20    #[serde(default)]
21    pub footer: Footer,
22    #[serde(default)]
23    pub status: Status,
24    #[serde(default)]
25    pub editor: Editor,
26}
27
28impl Default for Skin {
29    fn default() -> Self {
30        toml::from_str(include_str!("../assets/default.toml")).unwrap()
31    }
32}
33
34impl Skin {
35    pub(crate) fn from_file(file_path: &str) -> Result<Self, Box<dyn Error>> {
36        let f = shellexpand::env(file_path).map_or(file_path.to_string(), |x| x.to_string());
37
38        let toml_content = std::fs::read_to_string(f)?;
39
40        Ok(toml::from_str(&toml_content)?)
41    }
42
43    #[allow(clippy::too_many_lines)]
44    pub(crate) fn apply_to(&self, theme: &mut Theme) {
45        // Base
46        if let Some(target) = &self.base.focused {
47            set_fg_bg!(theme.base.focused, target, self.colors);
48        }
49        if let Some(target) = &self.base.unfocused {
50            set_fg_bg!(theme.base.unfocused, target, self.colors);
51        }
52
53        // Highlight
54        if let Some(target) = &self.highlight.focused {
55            set_fg_bg!(theme.highlight.focused, target, self.colors);
56        }
57        if let Some(target) = &self.highlight.unfocused {
58            set_fg_bg!(theme.highlight.unfocused, target, self.colors);
59        }
60
61        // Border
62        if let Some(target) = &self.border.unfocused {
63            if let Some(style) = &target.style {
64                set_fg_bg!(theme.border.unfocused, style, self.colors);
65            }
66            if let Some(border_type) = target.border_type {
67                theme.border.border_type_unfocused = border_type.into();
68            }
69        }
70        if let Some(target) = &self.border.focused {
71            if let Some(style) = &target.style {
72                set_fg_bg!(theme.border.focused, style, self.colors);
73            }
74            if let Some(border_type) = target.border_type {
75                theme.border.border_type_focused = border_type.into();
76            }
77        }
78
79        // Title
80        if let Some(target) = &self.title.focused {
81            set_fg_bg!(theme.title.focused, target, self.colors);
82        }
83        if let Some(target) = &self.title.unfocused {
84            set_fg_bg!(theme.title.unfocused, target, self.colors);
85        }
86        theme.title.focused = theme.title.focused.bold();
87        theme.title.unfocused = theme.title.unfocused.bold();
88
89        // Footer
90        if let Some(hide_footer) = self.footer.hide {
91            theme.hide_footer = hide_footer;
92        } else {
93            theme.hide_footer = false;
94        }
95
96        // Status
97        if let Some(hide_status) = self.status.hide {
98            theme.hide_status = hide_status;
99        } else {
100            theme.hide_status = false;
101        }
102
103        // Editor
104        if let Some(line_numbers) = self.editor.line_numbers {
105            theme.editor.line_numbers = line_numbers;
106        }
107    }
108}
109
110#[derive(Default, Debug, Deserialize)]
111pub(crate) struct Base {
112    pub focused: Option<FgBg>,
113    pub unfocused: Option<FgBg>,
114}
115
116#[derive(Default, Debug, Deserialize)]
117pub(crate) struct Highlight {
118    pub focused: Option<FgBg>,
119    pub unfocused: Option<FgBg>,
120}
121
122#[derive(Default, Debug, Deserialize)]
123pub(crate) struct Title {
124    pub focused: Option<FgBg>,
125    pub unfocused: Option<FgBg>,
126}
127
128#[derive(Debug, Deserialize, Default)]
129pub(crate) struct Border {
130    pub focused: Option<BorderConfig>,
131    pub unfocused: Option<BorderConfig>,
132}
133
134#[derive(Debug, Deserialize, Default)]
135pub(crate) struct BorderConfig {
136    #[serde(flatten)]
137    pub style: Option<FgBg>,
138    pub border_type: Option<SkinBorderType>,
139}
140
141/// Border type configuration that can be deserialized from skin files.
142#[derive(Debug, Clone, Copy, Deserialize)]
143#[serde(rename_all = "lowercase")]
144pub(crate) enum SkinBorderType {
145    Plain,
146    Rounded,
147    Double,
148    Thick,
149}
150
151impl From<SkinBorderType> for BorderType {
152    fn from(value: SkinBorderType) -> Self {
153        match value {
154            SkinBorderType::Plain => BorderType::Plain,
155            SkinBorderType::Rounded => BorderType::Rounded,
156            SkinBorderType::Double => BorderType::Double,
157            SkinBorderType::Thick => BorderType::Thick,
158        }
159    }
160}
161
162#[derive(Debug, Deserialize, Default)]
163pub(crate) struct Footer {
164    hide: Option<bool>,
165}
166
167#[derive(Debug, Deserialize, Default)]
168pub(crate) struct Status {
169    hide: Option<bool>,
170}
171
172#[derive(Debug, Deserialize, Default)]
173pub(crate) struct Editor {
174    pub line_numbers: Option<LineNumbers>,
175}
176
177#[derive(Debug, Deserialize, Default)]
178pub(crate) struct FgBg {
179    pub foreground: Option<String>,
180    pub background: Option<String>,
181}
182
183pub(crate) fn resolve_color(colors: &HashMap<String, Color>, color: Option<&str>) -> Option<Color> {
184    let color = color?;
185
186    if let Some(color) = colors.get(color) {
187        return Some(*color);
188    }
189
190    Color::from_str(color).ok()
191}
192
193pub(crate) mod macros {
194    #[macro_export]
195    macro_rules! set_fg_bg {
196        ($theme:expr, $fg_bg:expr, $colors:expr) => {
197            let fc = resolve_color(&$colors, $fg_bg.foreground.as_deref());
198            let bc = resolve_color(&$colors, $fg_bg.background.as_deref());
199            if let Some(fc) = fc {
200                $theme = $theme.fg(fc.0);
201            }
202            if let Some(bc) = bc {
203                $theme = $theme.bg(bc.0);
204            }
205        };
206    }
207}