1use crate::tokens::{MacAppearanceColors, MacDesignTokens};
4use egui::{Color32, Id};
5use std::sync::{Arc, OnceLock};
6
7pub mod accessors;
8pub mod contrast;
9pub mod pro_accessors;
10pub use contrast::{
11 alpha_composite, contrast_ratio, ensure_wcag_contrast, plus_darker, plus_lighter,
12 relative_luminance, srgb_channel_to_linear, MacBackgroundStyle,
13};
14
15static DEFAULT_TOKENS: OnceLock<Arc<MacDesignTokens>> = OnceLock::new();
16
17fn default_cached_tokens() -> Arc<MacDesignTokens> {
18 DEFAULT_TOKENS
19 .get_or_init(|| Arc::new(MacDesignTokens::load_default()))
20 .clone()
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
25pub enum MacAppearance {
26 #[default]
28 Aqua,
29 Anthracite,
31 DarkAqua,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum WindowControlsStyle {
38 #[default]
40 AppleTrafficLights,
41 WindowsCaptionButtons,
43 NativeHost,
45}
46
47#[derive(Debug, Clone)]
63pub struct MacTheme {
64 pub appearance: MacAppearance,
66 pub window_controls_style: WindowControlsStyle,
68 tokens: Arc<MacDesignTokens>,
69 custom_accent: Option<Color32>,
70}
71
72impl Default for MacTheme {
73 fn default() -> Self {
74 Self::new(MacAppearance::Aqua)
75 }
76}
77
78impl MacTheme {
79 pub fn new(appearance: MacAppearance) -> Self {
81 Self {
82 appearance,
83 window_controls_style: WindowControlsStyle::AppleTrafficLights,
84 tokens: default_cached_tokens(),
85 custom_accent: None,
86 }
87 }
88
89 pub fn from_ctx(ctx: &egui::Context) -> Self {
92 ctx.data(|d| d.get_temp::<MacTheme>(Id::NULL)).unwrap_or_default()
93 }
94
95 pub fn resolve<'a>(theme: Option<&'a Self>, ctx: &egui::Context) -> std::borrow::Cow<'a, Self> {
97 match theme {
98 Some(t) => std::borrow::Cow::Borrowed(t),
99 None => std::borrow::Cow::Owned(Self::from_ctx(ctx)),
100 }
101 }
102
103 pub fn resolve_or_default<'a>(theme: Option<&'a Self>) -> std::borrow::Cow<'a, Self> {
105 match theme {
106 Some(t) => std::borrow::Cow::Borrowed(t),
107 None => std::borrow::Cow::Owned(Self::default()),
108 }
109 }
110
111 pub fn with_window_controls_style(mut self, style: WindowControlsStyle) -> Self {
113 self.window_controls_style = style;
114 self
115 }
116
117 pub fn window_controls_style(&self) -> WindowControlsStyle { self.window_controls_style }
119
120 pub fn with_accent(mut self, accent: Color32) -> Self {
122 self.custom_accent = Some(accent);
123 self
124 }
125
126 pub fn with_tokens(appearance: MacAppearance, tokens: MacDesignTokens) -> Self {
128 Self { appearance, window_controls_style: WindowControlsStyle::AppleTrafficLights, tokens: Arc::new(tokens), custom_accent: None }
129 }
130
131 pub fn with_arc_tokens(appearance: MacAppearance, tokens: Arc<MacDesignTokens>) -> Self {
133 Self { appearance, window_controls_style: WindowControlsStyle::AppleTrafficLights, tokens, custom_accent: None }
134 }
135
136 pub fn tokens(&self) -> &MacDesignTokens { &self.tokens }
138
139 pub fn colors(&self) -> &MacAppearanceColors {
141 match self.appearance {
142 MacAppearance::Aqua => &self.tokens.colors().aqua,
143 MacAppearance::Anthracite => &self.tokens.colors().anthracite,
144 MacAppearance::DarkAqua => &self.tokens.colors().dark_aqua,
145 }
146 }
147
148 pub fn window_background(&self) -> Color32 { self.colors().window_background.to_color32() }
150 pub fn control_background(&self) -> Color32 { self.colors().control_background.to_color32() }
152 pub fn control(&self) -> Color32 { self.colors().control.to_color32() }
154 pub fn control_accent(&self) -> Color32 {
156 self.custom_accent.unwrap_or_else(|| self.colors().control_accent.to_color32())
157 }
158 pub fn selected_control(&self) -> Color32 {
160 self.custom_accent.unwrap_or_else(|| self.colors().selected_control.to_color32())
161 }
162 pub fn selected_control_inactive(&self) -> Color32 { self.colors().selected_control_inactive.to_color32() }
164 pub fn separator(&self) -> Color32 { self.colors().separator.to_color32() }
166 pub fn placeholder_text(&self) -> Color32 { self.colors().placeholder_text.to_color32() }
168 pub fn keyboard_focus_indicator(&self) -> Color32 { self.colors().keyboard_focus_indicator.to_color32() }
170 pub fn label(&self) -> Color32 { self.colors().label.to_color32() }
172 pub fn secondary_label(&self) -> Color32 { self.colors().secondary_label.to_color32() }
174 pub fn tertiary_label(&self) -> Color32 { self.colors().tertiary_label.to_color32() }
176 pub fn box_background(&self) -> Color32 { self.colors().box_background.to_color32() }
178 pub fn box_border(&self) -> Color32 { self.colors().box_border.to_color32() }
180 pub fn system_green(&self) -> Color32 { self.colors().system_green.to_color32() }
182 pub fn system_yellow(&self) -> Color32 { self.colors().system_yellow.to_color32() }
184 pub fn system_orange(&self) -> Color32 { self.colors().system_orange.to_color32() }
186 pub fn system_red(&self) -> Color32 { self.colors().system_red.to_color32() }
188 pub fn alternating_row_even(&self) -> Color32 { self.colors().alternating_row_even.to_color32() }
190 pub fn alternating_row_odd(&self) -> Color32 { self.colors().alternating_row_odd.to_color32() }
192 pub fn alternating_content_background(&self, row_index: usize) -> Color32 {
194 if row_index.is_multiple_of(2) { self.alternating_row_even() } else { self.alternating_row_odd() }
195 }
196 pub fn scrubber_selection_border(&self) -> Color32 { self.colors().scrubber_selection_border.to_color32() }
198 pub fn scrubber_outside_dim(&self) -> Color32 { self.colors().scrubber_outside_dim.to_color32() }
200 pub fn scrubber_waveform(&self) -> Color32 { self.colors().scrubber_waveform.to_color32() }
202 pub fn scrubber_playhead(&self) -> Color32 { self.colors().scrubber_playhead.to_color32() }
204 pub fn on_accent_text(&self) -> Color32 { self.colors().on_accent_text.to_color32() }
206 pub fn knob_fill(&self) -> Color32 { self.colors().knob_fill.to_color32() }
208 pub fn drop_shadow(&self) -> Color32 { self.colors().drop_shadow.to_color32() }
210 pub fn backdrop_dim(&self) -> Color32 { self.colors().backdrop_dim.to_color32() }
212 pub fn hud_background(&self) -> Color32 { self.colors().hud_background.to_color32() }
214 pub fn hud_border(&self) -> Color32 { self.colors().hud_border.to_color32() }
216 pub fn hud_text(&self) -> Color32 { self.colors().hud_text.to_color32() }
218 pub fn hud_secondary_text(&self) -> Color32 { self.colors().hud_secondary_text.to_color32() }
220 pub fn traffic_light_close(&self) -> (Color32, Color32) {
222 (self.colors().traffic_light_close.to_color32(), self.colors().traffic_light_close_border.to_color32())
223 }
224 pub fn traffic_light_minimize(&self) -> (Color32, Color32) {
226 (self.colors().traffic_light_minimize.to_color32(), self.colors().traffic_light_minimize_border.to_color32())
227 }
228 pub fn traffic_light_zoom(&self) -> (Color32, Color32) {
230 (self.colors().traffic_light_zoom.to_color32(), self.colors().traffic_light_zoom_border.to_color32())
231 }
232 pub fn traffic_light_glyph(&self) -> Color32 { self.colors().traffic_light_glyph.to_color32() }
234 pub fn windows_caption_close_hover_bg(&self) -> Color32 { self.colors().caption_close_hover.to_color32() }
236 pub fn windows_caption_close_pressed_bg(&self) -> Color32 { self.colors().caption_close_pressed.to_color32() }
238 pub fn windows_caption_hover_bg(&self) -> Color32 { self.colors().caption_hover.to_color32() }
240 pub fn windows_caption_pressed_bg(&self) -> Color32 { self.colors().caption_pressed.to_color32() }
242 pub fn scrollbar_thumb_color(&self, active: bool, alpha_factor: f32) -> Color32 {
244 let base = if active { self.colors().scrollbar_thumb_active.to_color32() } else { self.colors().scrollbar_thumb.to_color32() };
245 base.gamma_multiply(alpha_factor.clamp(0.0, 1.0))
246 }
247
248 pub fn apply_to_egui(&self, ctx: &egui::Context) {
250 let unchanged = ctx.data(|d| {
251 d.get_temp::<MacTheme>(Id::NULL).is_some_and(|prev| {
252 prev.appearance == self.appearance
253 && prev.window_controls_style == self.window_controls_style
254 && prev.custom_accent == self.custom_accent
255 && Arc::ptr_eq(&prev.tokens, &self.tokens)
256 })
257 });
258 if unchanged { return; }
259 ctx.data_mut(|d| d.insert_temp(Id::NULL, self.clone()));
260 let egui_theme = match self.appearance {
261 MacAppearance::Aqua => egui::Theme::Light,
262 MacAppearance::Anthracite | MacAppearance::DarkAqua => egui::Theme::Dark,
263 };
264 ctx.set_theme(egui_theme);
265 let mut visuals = match self.appearance {
266 MacAppearance::Aqua => egui::Visuals::light(),
267 MacAppearance::Anthracite | MacAppearance::DarkAqua => egui::Visuals::dark(),
268 };
269 let border_w = self.button().regular.border_width.points();
270 let label_color = self.label();
271 visuals.override_text_color = Some(label_color);
272 visuals.widgets.noninteractive.fg_stroke.color = label_color;
273 visuals.widgets.noninteractive.bg_stroke = egui::Stroke::new(border_w, self.separator());
274 visuals.widgets.inactive.fg_stroke.color = label_color;
275 visuals.widgets.inactive.bg_stroke = egui::Stroke::new(border_w, self.separator());
276 visuals.panel_fill = self.window_background();
277 visuals.window_fill = self.window_background();
278 visuals.window_stroke = egui::Stroke::new(border_w, self.separator());
279 let (small_pt, body_pt, heading_pt) = (self.text_field().small.font_size.points(), self.outline_view().font_size.points(), self.text_field().regular.font_size.points());
280 ctx.all_styles_mut(|style| {
281 use egui::{FontFamily, FontId, TextStyle};
282 style.text_styles = [
283 (TextStyle::Small, FontId::new(small_pt, FontFamily::Proportional)),
284 (TextStyle::Body, FontId::new(body_pt, FontFamily::Proportional)),
285 (TextStyle::Button, FontId::new(body_pt, FontFamily::Proportional)),
286 (TextStyle::Heading, FontId::new(heading_pt, FontFamily::Proportional)),
287 (TextStyle::Monospace, FontId::new(body_pt, FontFamily::Monospace)),
288 ].into();
289 });
290 ctx.set_visuals_of(egui_theme, visuals.clone());
291 ctx.set_visuals(visuals);
292 }
293}