1use egui::{Color32, Visuals};
2
3pub mod api;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
8pub enum ThemeMode {
9 Light,
11 Dark,
13 System,
15}
16
17pub struct TitleBarTheme {
19 pub background_color: Color32,
21 pub hover_color: Color32,
23 pub close_hover_color: Color32,
25 pub close_icon_color: Color32,
27 pub maximize_icon_color: Color32,
29 pub restore_icon_color: Color32,
31 pub minimize_icon_color: Color32,
33 pub title_color: Color32,
35 pub menu_text_color: Color32,
37 pub menu_text_size: f32,
39 pub menu_hover_color: Color32,
41 pub keyboard_selection_color: Color32,
43 pub submenu_background_color: Color32,
46 pub submenu_text_color: Color32,
48 pub submenu_text_size: f32,
50 pub submenu_hover_color: Color32,
52 pub submenu_disabled_color: Color32,
54 pub submenu_shortcut_color: Color32,
56 pub submenu_border_color: Color32,
58 pub submenu_keyboard_selection_color: Color32,
60}
61
62pub trait ThemeProvider: Send + Sync {
64 fn get_title_bar_theme(&self, theme_id: &str, mode: ThemeMode) -> Option<TitleBarTheme>;
66 fn get_egui_visuals(&self, theme_id: &str, mode: ThemeMode) -> Option<Visuals>;
68 fn list_available_themes(&self) -> Vec<String>;
70}
71
72#[derive(Debug)]
74pub enum ThemeError {
75 ThemeNotFound,
77}
78
79impl Default for TitleBarTheme {
80 fn default() -> Self {
81 Self::light()
82 }
83}
84
85impl TitleBarTheme {
86 pub fn light() -> Self {
88 Self {
89 background_color: Color32::WHITE,
90 hover_color: Color32::from_rgb(230, 230, 230),
91 close_hover_color: Color32::from_rgb(232, 17, 35),
92 close_icon_color: Color32::from_rgb(100, 100, 100),
93 maximize_icon_color: Color32::from_rgb(100, 100, 100),
94 restore_icon_color: Color32::from_rgb(100, 100, 100),
95 minimize_icon_color: Color32::from_rgb(100, 100, 100),
96 title_color: Color32::from_rgb(50, 50, 50),
97 menu_text_color: Color32::from_rgb(50, 50, 50),
98 menu_text_size: 12.0,
99 menu_hover_color: Color32::from_rgb(230, 230, 230),
100 keyboard_selection_color: Color32::from_rgb(0, 120, 215),
101 submenu_background_color: Color32::WHITE,
102 submenu_text_color: Color32::from_rgb(50, 50, 50),
103 submenu_text_size: 11.0,
104 submenu_hover_color: Color32::from_rgb(240, 240, 240),
105 submenu_disabled_color: Color32::from_rgb(150, 150, 150),
106 submenu_shortcut_color: Color32::from_rgb(100, 100, 100),
107 submenu_border_color: Color32::from_rgb(200, 200, 200),
108 submenu_keyboard_selection_color: Color32::from_rgb(0, 120, 215),
109 }
110 }
111
112 pub fn dark() -> Self {
114 Self {
115 background_color: Color32::from_rgb(30, 30, 30),
116 hover_color: Color32::from_rgb(60, 60, 60),
117 close_hover_color: Color32::from_rgb(232, 17, 35),
118 close_icon_color: Color32::from_rgb(200, 200, 200),
119 maximize_icon_color: Color32::from_rgb(200, 200, 200),
120 restore_icon_color: Color32::from_rgb(200, 200, 200),
121 minimize_icon_color: Color32::from_rgb(200, 200, 200),
122 title_color: Color32::from_rgb(200, 200, 200),
123 menu_text_color: Color32::from_rgb(200, 200, 200),
124 menu_text_size: 12.0,
125 menu_hover_color: Color32::from_rgb(60, 60, 60),
126 keyboard_selection_color: Color32::from_rgb(30, 144, 255),
127 submenu_background_color: Color32::from_rgb(40, 40, 40),
128 submenu_text_color: Color32::from_rgb(200, 200, 200),
129 submenu_text_size: 11.0,
130 submenu_hover_color: Color32::from_rgb(70, 70, 70),
131 submenu_disabled_color: Color32::from_rgb(120, 120, 120),
132 submenu_shortcut_color: Color32::from_rgb(160, 160, 160),
133 submenu_border_color: Color32::from_rgb(80, 80, 80),
134 submenu_keyboard_selection_color: Color32::from_rgb(30, 144, 255),
135 }
136 }
137
138 pub fn light_with_overrides(
140 background_color: Option<Color32>,
141 hover_color: Option<Color32>,
142 close_hover_color: Option<Color32>,
143 close_icon_color: Option<Color32>,
144 maximize_icon_color: Option<Color32>,
145 restore_icon_color: Option<Color32>,
146 minimize_icon_color: Option<Color32>,
147 title_color: Option<Color32>,
148 menu_text_color: Option<Color32>,
149 menu_text_size: Option<f32>,
150 menu_hover_color: Option<Color32>,
151 keyboard_selection_color: Option<Color32>,
152 submenu_background_color: Option<Color32>,
153 submenu_text_color: Option<Color32>,
154 submenu_hover_color: Option<Color32>,
155 submenu_shortcut_color: Option<Color32>,
156 submenu_keyboard_selection_color: Option<Color32>,
157 ) -> Self {
158 let default = Self::light();
159 Self {
160 background_color: background_color.unwrap_or(default.background_color),
161 hover_color: hover_color.unwrap_or(default.hover_color),
162 close_hover_color: close_hover_color.unwrap_or(default.close_hover_color),
163 close_icon_color: close_icon_color.unwrap_or(default.close_icon_color),
164 maximize_icon_color: maximize_icon_color.unwrap_or(default.maximize_icon_color),
165 restore_icon_color: restore_icon_color.unwrap_or(default.restore_icon_color),
166 minimize_icon_color: minimize_icon_color.unwrap_or(default.minimize_icon_color),
167 title_color: title_color.unwrap_or(default.title_color),
168 menu_text_color: menu_text_color.unwrap_or(default.menu_text_color),
169 menu_text_size: menu_text_size.unwrap_or(default.menu_text_size),
170 menu_hover_color: menu_hover_color.unwrap_or(default.menu_hover_color),
171 keyboard_selection_color: keyboard_selection_color
172 .unwrap_or(default.keyboard_selection_color),
173 submenu_background_color: submenu_background_color
174 .unwrap_or(default.submenu_background_color),
175 submenu_text_color: submenu_text_color.unwrap_or(default.submenu_text_color),
176 submenu_text_size: default.submenu_text_size,
177 submenu_hover_color: submenu_hover_color.unwrap_or(default.submenu_hover_color),
178 submenu_disabled_color: default.submenu_disabled_color,
179 submenu_shortcut_color: submenu_shortcut_color
180 .unwrap_or(default.submenu_shortcut_color),
181 submenu_border_color: default.submenu_border_color,
182 submenu_keyboard_selection_color: submenu_keyboard_selection_color
183 .unwrap_or(default.submenu_keyboard_selection_color),
184 }
185 }
186
187 pub fn dark_with_overrides(
189 background_color: Option<Color32>,
190 hover_color: Option<Color32>,
191 close_hover_color: Option<Color32>,
192 close_icon_color: Option<Color32>,
193 maximize_icon_color: Option<Color32>,
194 restore_icon_color: Option<Color32>,
195 minimize_icon_color: Option<Color32>,
196 title_color: Option<Color32>,
197 menu_text_color: Option<Color32>,
198 menu_text_size: Option<f32>,
199 menu_hover_color: Option<Color32>,
200 keyboard_selection_color: Option<Color32>,
201 submenu_background_color: Option<Color32>,
202 submenu_text_color: Option<Color32>,
203 submenu_hover_color: Option<Color32>,
204 submenu_shortcut_color: Option<Color32>,
205 submenu_keyboard_selection_color: Option<Color32>,
206 ) -> Self {
207 let default = Self::dark();
208 Self {
209 background_color: background_color.unwrap_or(default.background_color),
210 hover_color: hover_color.unwrap_or(default.hover_color),
211 close_hover_color: close_hover_color.unwrap_or(default.close_hover_color),
212 close_icon_color: close_icon_color.unwrap_or(default.close_icon_color),
213 maximize_icon_color: maximize_icon_color.unwrap_or(default.maximize_icon_color),
214 restore_icon_color: restore_icon_color.unwrap_or(default.restore_icon_color),
215 minimize_icon_color: minimize_icon_color.unwrap_or(default.minimize_icon_color),
216 title_color: title_color.unwrap_or(default.title_color),
217 menu_text_color: menu_text_color.unwrap_or(default.menu_text_color),
218 menu_text_size: menu_text_size.unwrap_or(default.menu_text_size),
219 menu_hover_color: menu_hover_color.unwrap_or(default.menu_hover_color),
220 keyboard_selection_color: keyboard_selection_color
221 .unwrap_or(default.keyboard_selection_color),
222 submenu_background_color: submenu_background_color
223 .unwrap_or(default.submenu_background_color),
224 submenu_text_color: submenu_text_color.unwrap_or(default.submenu_text_color),
225 submenu_text_size: default.submenu_text_size,
226 submenu_hover_color: submenu_hover_color.unwrap_or(default.submenu_hover_color),
227 submenu_disabled_color: default.submenu_disabled_color,
228 submenu_shortcut_color: submenu_shortcut_color
229 .unwrap_or(default.submenu_shortcut_color),
230 submenu_border_color: default.submenu_border_color,
231 submenu_keyboard_selection_color: submenu_keyboard_selection_color
232 .unwrap_or(default.submenu_keyboard_selection_color),
233 }
234 }
235}
236
237pub use ThemeMode::*;
238
239pub fn detect_system_dark_mode() -> bool {
241 #[cfg(target_os = "windows")]
242 {
243 use std::process::Command;
244
245 match Command::new("reg")
247 .args(&["query", "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "/v", "AppsUseLightTheme"])
248 .output()
249 {
250 Ok(output) => {
251 let output_str = String::from_utf8_lossy(&output.stdout);
252 !output_str.contains("0x1")
254 }
255 Err(_) => false, }
257 }
258
259 #[cfg(target_os = "macos")]
260 {
261 use std::process::Command;
262
263 match Command::new("defaults")
265 .args(&["read", "-g", "AppleInterfaceStyle"])
266 .output()
267 {
268 Ok(output) => {
269 let output_str = String::from_utf8_lossy(&output.stdout);
270 output_str.contains("Dark")
271 }
272 Err(_) => false, }
274 }
275
276 #[cfg(target_os = "linux")]
277 {
278 use std::process::Command;
279
280 if let Ok(output) = Command::new("gsettings")
282 .args(&["get", "org.gnome.desktop.interface", "gtk-theme"])
283 .output()
284 {
285 let output_str = String::from_utf8_lossy(&output.stdout);
286 return output_str.contains("dark") || output_str.contains("Dark");
287 }
288
289 std::env::var("GTK_THEME")
291 .map(|theme| theme.contains("dark") || theme.contains("Dark"))
292 .unwrap_or(false)
293 }
294
295 #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
296 {
297 false }
299}