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#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "lowercase")]
12pub enum LineNumbers {
13 #[default]
15 None,
16 Absolute,
18 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: BorderType,
63}
64
65impl Default for Border {
66 fn default() -> Self {
67 Self {
68 focused: Style::default(),
69 unfocused: Style::default(),
70 border_type: BorderType::Rounded,
71 }
72 }
73}
74
75#[derive(Debug, Clone)]
76pub struct Editor {
77 pub line_numbers: LineNumbers,
78}
79
80impl Default for Editor {
81 fn default() -> Self {
82 Self {
83 line_numbers: LineNumbers::Absolute,
84 }
85 }
86}
87
88impl Theme {
89 pub fn init(config: &Config) {
91 let skin = config
92 .skin
93 .as_deref()
94 .and_then(|skin_file| match skin::Skin::from_file(skin_file) {
95 Ok(skin) => Some(skin),
96 Err(err) => {
97 Logger::debug(format!(
98 "Failed to read skin from file {skin_file}, err: {err}"
99 ));
100 None
101 }
102 })
103 .unwrap_or_default();
104
105 let mut theme = Theme::default();
106 theme.update_from_skin(&skin);
107
108 let _ = THEME.set(theme.clone());
109 }
110
111 #[must_use]
113 pub fn global() -> &'static Theme {
114 THEME.get_or_init(|| {
115 Logger::debug("Theme was not initialized. Fallback to default.");
116 Theme::default()
117 })
118 }
119}