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_disabled_color: Option<Color32>,
156 submenu_shortcut_color: Option<Color32>,
157 submenu_keyboard_selection_color: Option<Color32>,
158 ) -> Self {
159 let default = Self::light();
160 Self {
161 background_color: background_color.unwrap_or(default.background_color),
162 hover_color: hover_color.unwrap_or(default.hover_color),
163 close_hover_color: close_hover_color.unwrap_or(default.close_hover_color),
164 close_icon_color: close_icon_color.unwrap_or(default.close_icon_color),
165 maximize_icon_color: maximize_icon_color.unwrap_or(default.maximize_icon_color),
166 restore_icon_color: restore_icon_color.unwrap_or(default.restore_icon_color),
167 minimize_icon_color: minimize_icon_color.unwrap_or(default.minimize_icon_color),
168 title_color: title_color.unwrap_or(default.title_color),
169 menu_text_color: menu_text_color.unwrap_or(default.menu_text_color),
170 menu_text_size: menu_text_size.unwrap_or(default.menu_text_size),
171 menu_hover_color: menu_hover_color.unwrap_or(default.menu_hover_color),
172 keyboard_selection_color: keyboard_selection_color
173 .unwrap_or(default.keyboard_selection_color),
174 submenu_background_color: submenu_background_color
175 .unwrap_or(default.submenu_background_color),
176 submenu_text_color: submenu_text_color.unwrap_or(default.submenu_text_color),
177 submenu_text_size: default.submenu_text_size,
178 submenu_hover_color: submenu_hover_color.unwrap_or(default.submenu_hover_color),
179 submenu_disabled_color: submenu_disabled_color
180 .unwrap_or(default.submenu_disabled_color),
181 submenu_shortcut_color: submenu_shortcut_color
182 .unwrap_or(default.submenu_shortcut_color),
183 submenu_border_color: default.submenu_border_color,
184 submenu_keyboard_selection_color: submenu_keyboard_selection_color
185 .unwrap_or(default.submenu_keyboard_selection_color),
186 }
187 }
188
189 pub fn dark_with_overrides(
191 background_color: Option<Color32>,
192 hover_color: Option<Color32>,
193 close_hover_color: Option<Color32>,
194 close_icon_color: Option<Color32>,
195 maximize_icon_color: Option<Color32>,
196 restore_icon_color: Option<Color32>,
197 minimize_icon_color: Option<Color32>,
198 title_color: Option<Color32>,
199 menu_text_color: Option<Color32>,
200 menu_text_size: Option<f32>,
201 menu_hover_color: Option<Color32>,
202 keyboard_selection_color: Option<Color32>,
203 submenu_background_color: Option<Color32>,
204 submenu_text_color: Option<Color32>,
205 submenu_hover_color: Option<Color32>,
206 submenu_disabled_color: Option<Color32>,
207 submenu_shortcut_color: Option<Color32>,
208 submenu_keyboard_selection_color: Option<Color32>,
209 ) -> Self {
210 let default = Self::dark();
211 Self {
212 background_color: background_color.unwrap_or(default.background_color),
213 hover_color: hover_color.unwrap_or(default.hover_color),
214 close_hover_color: close_hover_color.unwrap_or(default.close_hover_color),
215 close_icon_color: close_icon_color.unwrap_or(default.close_icon_color),
216 maximize_icon_color: maximize_icon_color.unwrap_or(default.maximize_icon_color),
217 restore_icon_color: restore_icon_color.unwrap_or(default.restore_icon_color),
218 minimize_icon_color: minimize_icon_color.unwrap_or(default.minimize_icon_color),
219 title_color: title_color.unwrap_or(default.title_color),
220 menu_text_color: menu_text_color.unwrap_or(default.menu_text_color),
221 menu_text_size: menu_text_size.unwrap_or(default.menu_text_size),
222 menu_hover_color: menu_hover_color.unwrap_or(default.menu_hover_color),
223 keyboard_selection_color: keyboard_selection_color
224 .unwrap_or(default.keyboard_selection_color),
225 submenu_background_color: submenu_background_color
226 .unwrap_or(default.submenu_background_color),
227 submenu_text_color: submenu_text_color.unwrap_or(default.submenu_text_color),
228 submenu_text_size: default.submenu_text_size,
229 submenu_hover_color: submenu_hover_color.unwrap_or(default.submenu_hover_color),
230 submenu_disabled_color: submenu_disabled_color
231 .unwrap_or(default.submenu_disabled_color),
232 submenu_shortcut_color: submenu_shortcut_color
233 .unwrap_or(default.submenu_shortcut_color),
234 submenu_border_color: default.submenu_border_color,
235 submenu_keyboard_selection_color: submenu_keyboard_selection_color
236 .unwrap_or(default.submenu_keyboard_selection_color),
237 }
238 }
239}
240
241pub use ThemeMode::*;
242
243pub fn detect_system_dark_mode() -> bool {
245 #[cfg(target_os = "windows")]
246 {
247 use std::process::Command;
248
249 match Command::new("reg")
251 .args(&["query", "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", "/v", "AppsUseLightTheme"])
252 .output()
253 {
254 Ok(output) => {
255 let output_str = String::from_utf8_lossy(&output.stdout);
256 !output_str.contains("0x1")
258 }
259 Err(_) => false, }
261 }
262
263 #[cfg(target_os = "macos")]
264 {
265 use std::process::Command;
266
267 match Command::new("defaults")
269 .args(&["read", "-g", "AppleInterfaceStyle"])
270 .output()
271 {
272 Ok(output) => {
273 let output_str = String::from_utf8_lossy(&output.stdout);
274 output_str.contains("Dark")
275 }
276 Err(_) => false, }
278 }
279
280 #[cfg(target_os = "linux")]
281 {
282 use std::process::Command;
283
284 if let Ok(output) = Command::new("gsettings")
286 .args(&["get", "org.gnome.desktop.interface", "gtk-theme"])
287 .output()
288 {
289 let output_str = String::from_utf8_lossy(&output.stdout);
290 return output_str.contains("dark") || output_str.contains("Dark");
291 }
292
293 std::env::var("GTK_THEME")
295 .map(|theme| theme.contains("dark") || theme.contains("Dark"))
296 .unwrap_or(false)
297 }
298
299 #[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
300 {
301 false }
303}