wireman_theme/
theme.rs

1use crate::{skin, Config};
2use logger::Logger;
3use once_cell::sync::OnceCell;
4use ratatui::{style::Style, widgets::BorderType};
5use serde::{Deserialize, Serialize};
6
7pub static THEME: OnceCell<Theme> = OnceCell::new();
8
9/// Configuration for line numbers in the editor.
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum LineNumbers {
13    /// Line numbers are disabled (default).
14    #[default]
15    None,
16    /// Display absolute line numbers.
17    Absolute,
18    /// Display relative line numbers.
19    Relative,
20}
21
22#[derive(Debug, Default, Clone)]
23pub struct Theme {
24    pub base: Base,
25    pub highlight: Highlight,
26    pub border: Border,
27    pub title: Title,
28    pub editor: Editor,
29    pub hide_footer: bool,
30    pub hide_status: bool,
31}
32
33impl Theme {
34    pub(crate) fn update_from_skin(&mut self, skin: &skin::Skin) {
35        skin.apply_to(self);
36    }
37}
38
39#[derive(Debug, Clone, Default)]
40pub struct Base {
41    pub focused: Style,
42    pub unfocused: Style,
43}
44
45#[derive(Debug, Clone, Default)]
46pub struct Highlight {
47    pub focused: Style,
48    pub unfocused: Style,
49}
50
51#[derive(Debug, Clone, Default)]
52pub struct Title {
53    pub focused: Style,
54    pub unfocused: Style,
55}
56
57#[allow(clippy::struct_field_names)]
58#[derive(Debug, Clone)]
59pub struct Border {
60    pub focused: Style,
61    pub unfocused: Style,
62    pub border_type_focused: BorderType,
63    pub border_type_unfocused: BorderType,
64}
65
66impl Default for Border {
67    fn default() -> Self {
68        Self {
69            focused: Style::default(),
70            unfocused: Style::default(),
71            border_type_focused: BorderType::Rounded,
72            border_type_unfocused: BorderType::Rounded,
73        }
74    }
75}
76
77#[derive(Debug, Clone)]
78pub struct Editor {
79    pub line_numbers: LineNumbers,
80}
81
82impl Default for Editor {
83    fn default() -> Self {
84        Self {
85            line_numbers: LineNumbers::Absolute,
86        }
87    }
88}
89
90impl Theme {
91    /// Initializes the `Theme` from a config.
92    pub fn init(config: &Config) {
93        let skin = config
94            .skin
95            .as_deref()
96            .and_then(|skin_file| match skin::Skin::from_file(skin_file) {
97                Ok(skin) => Some(skin),
98                Err(err) => {
99                    Logger::debug(format!(
100                        "Failed to read skin from file {skin_file}, err: {err}"
101                    ));
102                    None
103                }
104            })
105            .unwrap_or_default();
106
107        let mut theme = Theme::default();
108        theme.update_from_skin(&skin);
109
110        let _ = THEME.set(theme.clone());
111    }
112
113    /// Gets the globally shared theme data
114    #[must_use]
115    pub fn global() -> &'static Theme {
116        THEME.get_or_init(|| {
117            Logger::debug("Theme was not initialized. Fallback to default.");
118            Theme::default()
119        })
120    }
121}