1use crate::{skin, Config};
2use logger::Logger;
3use once_cell::sync::OnceCell;
4use ratatui::{layout::Direction, 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 layout: Layout,
25 pub base: Base,
26 pub highlight: Highlight,
27 pub border: Border,
28 pub title: Title,
29 pub editor: Editor,
30 pub hide_footer: bool,
31 pub hide_status: bool,
32}
33
34impl Theme {
35 pub(crate) fn update_from_skin(&mut self, skin: &skin::Skin) {
36 skin.apply_to(self);
37 }
38}
39
40#[derive(Debug, Clone, Default)]
41pub struct Layout {
42 pub main_split: Direction,
43}
44
45#[derive(Debug, Clone, Default)]
46pub struct Base {
47 pub focused: Style,
48 pub unfocused: Style,
49}
50
51#[derive(Debug, Clone, Default)]
52pub struct Highlight {
53 pub focused: Style,
54 pub unfocused: Style,
55}
56
57#[derive(Debug, Clone, Default)]
58pub struct Title {
59 pub focused: Style,
60 pub unfocused: Style,
61}
62
63#[allow(clippy::struct_field_names)]
64#[derive(Debug, Clone)]
65pub struct Border {
66 pub focused: Style,
67 pub unfocused: Style,
68 pub border_type_focused: BorderType,
69 pub border_type_unfocused: BorderType,
70}
71
72impl Default for Border {
73 fn default() -> Self {
74 Self {
75 focused: Style::default(),
76 unfocused: Style::default(),
77 border_type_focused: BorderType::Rounded,
78 border_type_unfocused: BorderType::Rounded,
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct Editor {
85 pub line_numbers: LineNumbers,
86}
87
88impl Default for Editor {
89 fn default() -> Self {
90 Self {
91 line_numbers: LineNumbers::Absolute,
92 }
93 }
94}
95
96impl Theme {
97 pub fn init(config: &Config) {
99 let skin = config
100 .skin
101 .as_deref()
102 .and_then(|skin_file| match skin::Skin::from_file(skin_file) {
103 Ok(skin) => Some(skin),
104 Err(err) => {
105 Logger::debug(format!(
106 "Failed to read skin from file {skin_file}, err: {err}"
107 ));
108 None
109 }
110 })
111 .unwrap_or_default();
112
113 let mut theme = Theme::default();
114 theme.update_from_skin(&skin);
115
116 let _ = THEME.set(theme.clone());
117 }
118
119 #[must_use]
121 pub fn global() -> &'static Theme {
122 THEME.get_or_init(|| {
123 Logger::debug("Theme was not initialized. Fallback to default.");
124 Theme::default()
125 })
126 }
127}