1use {
2 ratatui::style::Color,
3 std::sync::atomic::{AtomicPtr, AtomicU8, Ordering},
4};
5
6pub mod dark;
7pub mod forest;
8pub mod light;
9pub mod midnight;
10pub mod pastel;
11pub mod sunrise;
12
13pub use {
14 dark::DARK_THEME, forest::FOREST_THEME, light::LIGHT_THEME, midnight::MIDNIGHT_THEME,
15 pastel::PASTEL_THEME, sunrise::SUNRISE_THEME,
16};
17
18#[derive(Clone, Copy, Debug)]
19#[allow(dead_code)]
20pub struct Theme {
21 pub background: Color,
22 pub surface: Color,
23 pub panel: Color,
25 pub text: Color,
26 pub text_secondary: Color,
27 pub hint: Color,
29 pub menu: Color,
31 pub accent: Color,
32 pub accent_text: Color,
33 pub highlight: Color,
34 pub target: Color,
35 pub warning: Color,
36 pub warning_text: Color,
37 pub success: Color,
38 pub success_text: Color,
39 pub error: Color,
40 pub error_text: Color,
41 pub inactive_text: Color,
43 pub inactive_bg: Color,
45 pub crumb_a: Color,
47 pub crumb_b: Color,
48 pub crumb_icon: Color,
50}
51
52const fn theme_ptr(theme: &'static Theme) -> *mut Theme {
53 theme as *const Theme as *mut Theme
54}
55
56static CURRENT_THEME_PTR: AtomicPtr<Theme> = AtomicPtr::new(theme_ptr(&DARK_THEME));
57static CURRENT_THEME_ID: AtomicU8 = AtomicU8::new(ThemeId::Dark as u8);
58
59pub struct ThemeWrapper;
60
61impl std::ops::Deref for ThemeWrapper {
62 type Target = Theme;
63
64 fn deref(&self) -> &Self::Target {
65 unsafe { &*CURRENT_THEME_PTR.load(Ordering::Relaxed) }
67 }
68}
69
70pub static THEME: ThemeWrapper = ThemeWrapper;
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73#[repr(u8)]
74pub enum ThemeId {
75 Dark = 0,
76 Light = 1,
77 Pastel = 2,
78 Sunrise = 3,
79 Midnight = 4,
80 Forest = 5,
81}
82
83impl ThemeId {
84 pub const fn as_str(self) -> &'static str {
85 match self {
86 ThemeId::Dark => "dark",
87 ThemeId::Light => "light",
88 ThemeId::Pastel => "pastel",
89 ThemeId::Sunrise => "sunrise",
90 ThemeId::Midnight => "midnight",
91 ThemeId::Forest => "forest",
92 }
93 }
94}
95
96impl std::str::FromStr for ThemeId {
97 type Err = ();
98
99 fn from_str(value: &str) -> Result<Self, Self::Err> {
100 match value {
101 "dark" => Ok(ThemeId::Dark),
102 "light" => Ok(ThemeId::Light),
103 "pastel" => Ok(ThemeId::Pastel),
104 "sunrise" => Ok(ThemeId::Sunrise),
105 "midnight" => Ok(ThemeId::Midnight),
106 "forest" => Ok(ThemeId::Forest),
107 _ => Err(()),
108 }
109 }
110}
111
112pub struct ThemePreset {
113 pub id: ThemeId,
114 pub key: char,
115 pub label: &'static str,
116 pub theme: &'static Theme,
117}
118
119pub const PRESETS: [ThemePreset; 6] = [
120 ThemePreset {
121 id: ThemeId::Dark,
122 key: 'd',
123 label: "Dark",
124 theme: &DARK_THEME,
125 },
126 ThemePreset {
127 id: ThemeId::Light,
128 key: 'l',
129 label: "Light",
130 theme: &LIGHT_THEME,
131 },
132 ThemePreset {
133 id: ThemeId::Pastel,
134 key: 'p',
135 label: "Pastel",
136 theme: &PASTEL_THEME,
137 },
138 ThemePreset {
139 id: ThemeId::Sunrise,
140 key: 's',
141 label: "Sunrise",
142 theme: &SUNRISE_THEME,
143 },
144 ThemePreset {
145 id: ThemeId::Midnight,
146 key: 'm',
147 label: "Midnight",
148 theme: &MIDNIGHT_THEME,
149 },
150 ThemePreset {
151 id: ThemeId::Forest,
152 key: 'f',
153 label: "Forest",
154 theme: &FOREST_THEME,
155 },
156];
157
158pub fn preset_index(id: ThemeId) -> usize {
159 id as usize
160}
161
162pub fn preset(id: ThemeId) -> &'static ThemePreset {
163 &PRESETS[preset_index(id)]
164}
165
166pub fn preset_by_key(key: char) -> Option<&'static ThemePreset> {
167 PRESETS.iter().find(|preset| preset.key == key)
168}
169
170pub fn set_theme(id: ThemeId) {
171 let preset = preset(id);
172 CURRENT_THEME_PTR.store(theme_ptr(preset.theme), Ordering::Relaxed);
173 CURRENT_THEME_ID.store(id as u8, Ordering::Relaxed);
174}
175
176pub fn current_theme_id() -> ThemeId {
177 match CURRENT_THEME_ID.load(Ordering::Relaxed) {
178 0 => ThemeId::Dark,
179 1 => ThemeId::Light,
180 2 => ThemeId::Pastel,
181 3 => ThemeId::Sunrise,
182 4 => ThemeId::Midnight,
183 5 => ThemeId::Forest,
184 _ => ThemeId::Dark,
185 }
186}