Skip to main content

glues_tui/
theme.rs

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    /// background for status bars or popups
24    pub panel: Color,
25    pub text: Color,
26    pub text_secondary: Color,
27    /// hint or description text
28    pub hint: Color,
29    /// menu item text
30    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    /// text for disabled or unfocused elements
42    pub inactive_text: Color,
43    /// background for inactive highlights such as tabs
44    pub inactive_bg: Color,
45    /// alternating breadcrumb background colors
46    pub crumb_a: Color,
47    pub crumb_b: Color,
48    /// symbol color for breadcrumbs and directory icons
49    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        // SAFETY: the pointer always refers to one of the &'static Theme presets.
66        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}