1use crate::tokens::{
8 ButtonMetrics, ColorWellMetrics, FocusRingMetrics, LayoutMetrics, MacAppearanceColors,
9 MacControlMetrics, MacDesignTokens, MacMotionTokens, MacTypographyTokens, OutlineViewMetrics,
10 PopUpButtonMetrics, PopoverMetrics, ScrollViewMetrics, SearchFieldMetrics, SegmentedMetrics,
11 SliderMetrics, SplitViewMetrics, StackViewMetrics, StepperMetrics, TextFieldMetrics,
12 ThumbnailCellMetrics, ToggleMetrics, ToolbarMetrics,
13};
14use egui::Color32;
15
16use std::sync::{Arc, OnceLock};
17
18pub mod contrast;
19pub use contrast::{
20 contrast_ratio, relative_luminance, srgb_channel_to_linear, MacBackgroundStyle,
21};
22
23static DEFAULT_TOKENS: OnceLock<Arc<MacDesignTokens>> = OnceLock::new();
24
25fn default_cached_tokens() -> Arc<MacDesignTokens> {
26 DEFAULT_TOKENS
27 .get_or_init(|| Arc::new(MacDesignTokens::load_default()))
28 .clone()
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub enum MacAppearance {
34 #[default]
36 Aqua,
37 DarkAqua,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum WindowControlsStyle {
44 #[default]
46 AppleTrafficLights,
47 WindowsCaptionButtons,
49 NativeHost,
51}
52
53#[derive(Debug, Clone)]
70pub struct MacTheme {
71 pub appearance: MacAppearance,
73 pub window_controls_style: WindowControlsStyle,
75 tokens: Arc<MacDesignTokens>,
76 custom_accent: Option<Color32>,
77}
78
79impl Default for MacTheme {
80 fn default() -> Self {
81 Self::new(MacAppearance::Aqua)
82 }
83}
84
85impl MacTheme {
86 pub fn new(appearance: MacAppearance) -> Self {
88 Self {
89 appearance,
90 window_controls_style: WindowControlsStyle::AppleTrafficLights,
91 tokens: default_cached_tokens(),
92 custom_accent: None,
93 }
94 }
95
96 pub fn with_window_controls_style(mut self, style: WindowControlsStyle) -> Self {
98 self.window_controls_style = style;
99 self
100 }
101
102 pub fn window_controls_style(&self) -> WindowControlsStyle {
104 self.window_controls_style
105 }
106
107 pub fn with_accent(mut self, accent: Color32) -> Self {
109 self.custom_accent = Some(accent);
110 self
111 }
112
113 pub fn with_tokens(appearance: MacAppearance, tokens: MacDesignTokens) -> Self {
115 Self {
116 appearance,
117 window_controls_style: WindowControlsStyle::AppleTrafficLights,
118 tokens: Arc::new(tokens),
119 custom_accent: None,
120 }
121 }
122
123 pub fn with_arc_tokens(appearance: MacAppearance, tokens: Arc<MacDesignTokens>) -> Self {
125 Self {
126 appearance,
127 window_controls_style: WindowControlsStyle::AppleTrafficLights,
128 tokens,
129 custom_accent: None,
130 }
131 }
132
133 pub fn tokens(&self) -> &MacDesignTokens {
135 &self.tokens
136 }
137
138 pub fn colors(&self) -> &MacAppearanceColors {
140 match self.appearance {
141 MacAppearance::Aqua => &self.tokens.colors().aqua,
142 MacAppearance::DarkAqua => &self.tokens.colors().dark_aqua,
143 }
144 }
145
146 pub fn window_background(&self) -> Color32 {
148 self.colors().window_background.to_color32()
149 }
150
151 pub fn control_background(&self) -> Color32 {
153 self.colors().control_background.to_color32()
154 }
155
156 pub fn control(&self) -> Color32 {
158 self.colors().control.to_color32()
159 }
160
161 pub fn control_accent(&self) -> Color32 {
163 self.custom_accent
164 .unwrap_or_else(|| self.colors().control_accent.to_color32())
165 }
166
167 pub fn selected_control(&self) -> Color32 {
169 self.custom_accent
170 .unwrap_or_else(|| self.colors().selected_control.to_color32())
171 }
172
173 pub fn selected_control_inactive(&self) -> Color32 {
175 self.colors().selected_control_inactive.to_color32()
176 }
177
178 pub fn separator(&self) -> Color32 {
180 self.colors().separator.to_color32()
181 }
182
183 pub fn placeholder_text(&self) -> Color32 {
185 self.colors().placeholder_text.to_color32()
186 }
187
188 pub fn keyboard_focus_indicator(&self) -> Color32 {
190 self.colors().keyboard_focus_indicator.to_color32()
191 }
192
193 pub fn label(&self) -> Color32 {
195 self.colors().label.to_color32()
196 }
197
198 pub fn secondary_label(&self) -> Color32 {
200 self.colors().secondary_label.to_color32()
201 }
202
203 pub fn apply_to_egui(&self, ctx: &egui::Context) {
205 let egui_theme = match self.appearance {
206 MacAppearance::Aqua => egui::Theme::Light,
207 MacAppearance::DarkAqua => egui::Theme::Dark,
208 };
209 ctx.set_theme(egui_theme);
210
211 let mut visuals = match self.appearance {
212 MacAppearance::Aqua => egui::Visuals::light(),
213 MacAppearance::DarkAqua => egui::Visuals::dark(),
214 };
215
216 let label_color = self.label();
217 visuals.override_text_color = Some(label_color);
218 visuals.widgets.noninteractive.fg_stroke.color = label_color;
219 visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, self.separator());
220 visuals.widgets.inactive.fg_stroke.color = label_color;
221 visuals.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, self.separator());
222 visuals.panel_fill = self.window_background();
223 visuals.window_fill = self.window_background();
224 visuals.window_stroke = egui::Stroke::new(1.0, self.separator());
225
226 ctx.all_styles_mut(|style| {
227 use egui::{FontFamily, FontId, TextStyle};
228 style.text_styles = [
229 (TextStyle::Small, FontId::new(11.0, FontFamily::Proportional)),
230 (TextStyle::Body, FontId::new(12.0, FontFamily::Proportional)),
231 (TextStyle::Button, FontId::new(12.0, FontFamily::Proportional)),
232 (TextStyle::Heading, FontId::new(13.0, FontFamily::Proportional)),
233 (TextStyle::Monospace, FontId::new(12.0, FontFamily::Monospace)),
234 ]
235 .into();
236 });
237
238 ctx.set_visuals_of(egui_theme, visuals.clone());
239 ctx.set_visuals(visuals);
240 }
241
242 pub fn control_metrics(&self) -> &MacControlMetrics {
244 self.tokens.control_metrics()
245 }
246
247 pub fn focus_ring(&self) -> &FocusRingMetrics {
249 &self.control_metrics().focus_ring
250 }
251
252 pub fn text_field(&self) -> &TextFieldMetrics {
254 &self.control_metrics().text_field
255 }
256
257 pub fn button(&self) -> &ButtonMetrics {
259 &self.control_metrics().button
260 }
261
262 pub fn segmented(&self) -> &SegmentedMetrics {
264 &self.control_metrics().segmented
265 }
266
267 pub fn popover_metrics(&self) -> &PopoverMetrics {
269 self.tokens.popover_metrics()
270 }
271
272 pub fn toolbar_metrics(&self) -> &ToolbarMetrics {
274 self.tokens.toolbar_metrics()
275 }
276
277 pub fn slider(&self) -> &SliderMetrics {
279 self.tokens.slider_metrics()
280 }
281
282 pub fn thumbnail(&self) -> &ThumbnailCellMetrics {
284 self.tokens.thumbnail_metrics()
285 }
286
287 pub fn split_view(&self) -> &SplitViewMetrics {
289 self.tokens.split_view_metrics()
290 }
291
292 pub fn layout_metrics(&self) -> &LayoutMetrics {
294 self.tokens.layout_metrics()
295 }
296
297 pub fn scroll_view(&self) -> &ScrollViewMetrics {
299 self.tokens.scroll_view_metrics()
300 }
301
302 pub fn stack_view(&self) -> &StackViewMetrics {
304 self.tokens.stack_view_metrics()
305 }
306
307 pub fn outline_view(&self) -> &OutlineViewMetrics {
309 self.tokens.outline_view_metrics()
310 }
311
312 pub fn popup_button(&self) -> &PopUpButtonMetrics {
314 self.tokens.popup_button_metrics()
315 }
316
317 pub fn search_field(&self) -> &SearchFieldMetrics {
319 self.tokens.search_field_metrics()
320 }
321
322 pub fn stepper(&self) -> &StepperMetrics {
324 self.tokens.stepper_metrics()
325 }
326
327 pub fn color_well(&self) -> &ColorWellMetrics {
329 self.tokens.color_well_metrics()
330 }
331
332 pub fn toggle(&self) -> &ToggleMetrics {
334 self.tokens.toggle_metrics()
335 }
336
337 pub fn motion(&self) -> &MacMotionTokens {
339 self.tokens.motion()
340 }
341
342 pub fn typography(&self) -> &MacTypographyTokens {
344 self.tokens.typography()
345 }
346
347 pub fn system_green(&self) -> Color32 {
349 self.colors().system_green.to_color32()
350 }
351
352 pub fn traffic_light_close(&self) -> (Color32, Color32) {
354 (
355 self.colors().traffic_light_close.to_color32(),
356 self.colors().traffic_light_close_border.to_color32(),
357 )
358 }
359
360 pub fn traffic_light_minimize(&self) -> (Color32, Color32) {
362 (
363 self.colors().traffic_light_minimize.to_color32(),
364 self.colors().traffic_light_minimize_border.to_color32(),
365 )
366 }
367
368 pub fn traffic_light_zoom(&self) -> (Color32, Color32) {
370 (
371 self.colors().traffic_light_zoom.to_color32(),
372 self.colors().traffic_light_zoom_border.to_color32(),
373 )
374 }
375
376 pub fn windows_caption_close_hover_bg(&self) -> Color32 {
378 Color32::from_rgb(232, 17, 35)
379 }
380
381 pub fn windows_caption_close_pressed_bg(&self) -> Color32 {
383 Color32::from_rgb(196, 43, 28)
384 }
385
386 pub fn windows_caption_hover_bg(&self) -> Color32 {
388 match self.appearance {
389 MacAppearance::Aqua => Color32::from_black_alpha(15),
390 MacAppearance::DarkAqua => Color32::from_white_alpha(20),
391 }
392 }
393
394 pub fn windows_caption_pressed_bg(&self) -> Color32 {
396 match self.appearance {
397 MacAppearance::Aqua => Color32::from_black_alpha(25),
398 MacAppearance::DarkAqua => Color32::from_white_alpha(35),
399 }
400 }
401
402 pub fn background_style(&self, bg: Color32) -> MacBackgroundStyle {
404 MacBackgroundStyle::from_color(bg)
405 }
406
407 pub fn window_background_style(&self) -> MacBackgroundStyle {
409 MacBackgroundStyle::from_color(self.window_background())
410 }
411
412 pub fn control_background_style(&self) -> MacBackgroundStyle {
414 MacBackgroundStyle::from_color(self.control_background())
415 }
416
417 pub fn selected_control_background_style(&self) -> MacBackgroundStyle {
419 MacBackgroundStyle::from_color(self.selected_control())
420 }
421}