Skip to main content

egui_macview/theme/
accessors.rs

1//! Metric, Typography, Motion, and Contrast Accessors on [`MacTheme`].
2
3use super::{MacBackgroundStyle, MacTheme};
4use crate::tokens::{
5    BoxViewMetrics, ButtonMetrics, CheckboxMetrics, CollectionViewMetrics, ColorWellMetrics,
6    ComboBoxMetrics, ContextMenuMetrics, DatePickerMetrics, DisclosureGroupMetrics,
7    FocusRingMetrics, FooterMetrics, HelpButtonMetrics, InlineActionButtonMetrics, LabelMetrics,
8    LayoutMetrics, LevelIndicatorMetrics, MacControlMetrics, MacMotionTokens, MacTypographyTokens,
9    MenuBarMetrics, NavigationMetrics, OutlineViewMetrics, PaletteMetrics, PathControlMetrics,
10    PlayerViewMetrics, PopUpButtonMetrics, PopoverMetrics, ProgressIndicatorMetrics,
11    RuleEditorMetrics, ScrollViewMetrics, SearchFieldMetrics, SegmentedMetrics, SliderMetrics,
12    SplitViewMetrics, StackViewMetrics, StepperMetrics, TabViewMetrics, TableViewMetrics,
13    TextFieldMetrics, ThumbnailCellMetrics, ToastMetrics, ToggleMetrics, TokenFieldMetrics,
14    ToolbarMetrics, TrimScrubberMetrics,
15};
16use egui::{Color32, FontId, Galley, TextFormat, Ui};
17use std::sync::Arc;
18
19const fn tracking_table_key(pt: i32) -> Option<&'static str> {
20    match pt {
21        6 => Some("pt_6"),
22        7 => Some("pt_7"),
23        8 => Some("pt_8"),
24        9 => Some("pt_9"),
25        10 => Some("pt_10"),
26        11 => Some("pt_11"),
27        12 => Some("pt_12"),
28        13 => Some("pt_13"),
29        14 => Some("pt_14"),
30        15 => Some("pt_15"),
31        16 => Some("pt_16"),
32        17 => Some("pt_17"),
33        18 => Some("pt_18"),
34        19 => Some("pt_19"),
35        20 => Some("pt_20"),
36        21 => Some("pt_21"),
37        22 => Some("pt_22"),
38        23 => Some("pt_23"),
39        24 => Some("pt_24"),
40        _ => None,
41    }
42}
43
44impl MacTheme {
45    /// Resolves Apple HIG / SF Pro optical tracking (`extra_letter_spacing` in points)
46    /// for `size_pt` from `self.typography().tracking_table`, falling back to
47    /// [`crate::tokens::sf_pro_tracking`] for fractional or out-of-range font sizes.
48    pub fn tracking_for_font_size(&self, size_pt: f32) -> f32 {
49        let rounded = size_pt.round();
50        if (size_pt - rounded).abs() == 0.0 {
51            if let Some(key) = tracking_table_key(rounded as i32) {
52                if let Some(entry) = self.typography().tracking_table.get(key) {
53                    return entry.tracking_pt;
54                }
55            }
56        }
57        crate::tokens::sf_pro_tracking(size_pt)
58    }
59
60    /// Resolves the `Inter-SemiBold` [`FontId`] when registered in `ui.ctx()`, or falls back
61    /// to [`FontId::proportional`], returning `(font_id, has_semibold_family)` with zero
62    /// per-widget `Arc<str>` or `Vec<FontFamily>` heap allocations.
63    pub fn semibold_font_id(&self, ui: &Ui, size_pt: f32) -> (FontId, bool) {
64        static INTER_SEMIBOLD_FAMILY: std::sync::OnceLock<egui::FontFamily> =
65            std::sync::OnceLock::new();
66        let family = INTER_SEMIBOLD_FAMILY.get_or_init(|| {
67            egui::FontFamily::Name(crate::symbols::INTER_SEMIBOLD_FONT_NAME.into())
68        });
69        let cache_id = egui::Id::unique("macview_has_inter_semibold");
70        let pass_nr = ui.ctx().cumulative_pass_nr();
71        let cached = ui.ctx().data(|d| d.get_temp::<(u64, bool)>(cache_id));
72        let has_semibold = match cached {
73            Some((_, true)) => true,
74            Some((cached_pass, false)) if cached_pass == pass_nr => false,
75            _ => {
76                let found = ui.fonts(|f| f.families().contains(family));
77                ui.ctx().data_mut(|d| d.insert_temp(cache_id, (pass_nr, found)));
78                found
79            }
80        };
81        if has_semibold {
82            (FontId::new(size_pt, family.clone()), true)
83        } else {
84            (FontId::proportional(size_pt), false)
85        }
86    }
87
88    /// Constructs an [`egui::TextFormat`] populated with optical letter spacing (`extra_letter_spacing`)
89    /// from the Apple HIG tracking table for `font_id.size`.
90    pub fn tracked_text_format(&self, font_id: FontId, color: Color32) -> TextFormat {
91        TextFormat {
92            font_id: font_id.clone(),
93            color,
94            extra_letter_spacing: self.tracking_for_font_size(font_id.size),
95            ..Default::default()
96        }
97    }
98
99    /// Lays out a text [`Galley`] with Apple HIG optical tracking (`extra_letter_spacing`)
100    /// automatically applied from `self.typography().tracking_table`.
101    pub fn layout_tracked_galley(
102        &self,
103        ui: &Ui,
104        text: &str,
105        font_id: FontId,
106        color: Color32,
107        wrap_width: f32,
108    ) -> Arc<Galley> {
109        let mut job = egui::text::LayoutJob::single_section(
110            text.to_owned(),
111            self.tracked_text_format(font_id, color),
112        );
113        job.wrap.max_width = wrap_width;
114        ui.painter().layout_job(job)
115    }
116
117    /// Returns control metrics tokens.
118    pub fn control_metrics(&self) -> &MacControlMetrics { self.tokens.control_metrics() }
119    /// Returns button size and geometry tokens.
120    pub fn button(&self) -> &ButtonMetrics { &self.tokens.control_metrics().button }
121    /// Returns text field and input cell size/geometry tokens.
122    pub fn text_field(&self) -> &TextFieldMetrics { &self.tokens.control_metrics().text_field }
123    /// Returns segmented control geometry tokens.
124    pub fn segmented(&self) -> &SegmentedMetrics { &self.tokens.control_metrics().segmented }
125    /// Returns focus ring halo tokens.
126    pub fn focus_ring(&self) -> &FocusRingMetrics { &self.tokens.control_metrics().focus_ring }
127    /// Returns toolbar layout and hit-target tokens.
128    pub fn toolbar_metrics(&self) -> &ToolbarMetrics { self.tokens.toolbar_metrics() }
129    /// Returns popover and floating panel geometry tokens.
130    pub fn popover_metrics(&self) -> &PopoverMetrics { self.tokens.popover_metrics() }
131    /// Returns split view divider and pane geometry tokens.
132    pub fn split_view(&self) -> &SplitViewMetrics { self.tokens.split_view_metrics() }
133    /// Returns layout container metrics (`scroll_view`, `stack_view`).
134    pub fn layout_metrics(&self) -> &LayoutMetrics { self.tokens.layout_metrics() }
135    /// Returns scroll view overlay scrollbar geometry tokens.
136    pub fn scroll_view(&self) -> &ScrollViewMetrics { self.tokens.scroll_view_metrics() }
137    /// Returns stack view spacing, padding, and header tokens.
138    pub fn stack_view(&self) -> &StackViewMetrics { self.tokens.stack_view_metrics() }
139    /// Returns outline view row, indentation, and chevron tokens.
140    pub fn outline_view(&self) -> &OutlineViewMetrics { self.tokens.outline_view_metrics() }
141    /// Returns slider track, thumb, and tick tokens.
142    pub fn slider(&self) -> &SliderMetrics { self.tokens.slider_metrics() }
143    /// Returns thumbnail cell preview tokens.
144    pub fn thumbnail(&self) -> &ThumbnailCellMetrics { self.tokens.thumbnail_metrics() }
145    /// Returns pop-up button bezel and menu tokens.
146    pub fn popup_button(&self) -> &PopUpButtonMetrics { self.tokens.popup_button_metrics() }
147    /// Returns search field pill, icon, and clear button tokens.
148    pub fn search_field(&self) -> &SearchFieldMetrics { self.tokens.search_field_metrics() }
149    /// Returns numeric stepper control tokens.
150    pub fn stepper(&self) -> &StepperMetrics { self.tokens.stepper_metrics() }
151    /// Returns color well swatch and picker tokens.
152    pub fn color_well(&self) -> &ColorWellMetrics { self.tokens.color_well_metrics() }
153    /// Returns toggle switch (`NSSwitch`) tokens.
154    pub fn toggle(&self) -> &ToggleMetrics { self.tokens.toggle_metrics() }
155    /// Returns navigation and feedback metrics tokens.
156    pub fn navigation_metrics(&self) -> &NavigationMetrics { self.tokens.navigation_metrics() }
157    /// Returns menu bar and dropdown menu tokens.
158    pub fn menu_bar_metrics(&self) -> &MenuBarMetrics { self.tokens.menu_bar_metrics() }
159    /// Returns canvas footer bar tokens.
160    pub fn footer_metrics(&self) -> &FooterMetrics { self.tokens.footer_metrics() }
161    /// Returns Spotlight command palette tokens.
162    pub fn palette_metrics(&self) -> &PaletteMetrics { self.tokens.palette_metrics() }
163    /// Returns context menu popup tokens.
164    pub fn context_menu_metrics(&self) -> &ContextMenuMetrics { self.tokens.context_menu_metrics() }
165    /// Returns floating toast notification tokens.
166    pub fn toast_metrics(&self) -> &ToastMetrics { self.tokens.toast_metrics() }
167    /// Returns progress indicator bar and spinning fin tokens.
168    pub fn progress_indicator(&self) -> &ProgressIndicatorMetrics { self.tokens.progress_indicator_metrics() }
169    /// Returns table view and sortable header tokens.
170    pub fn table_view(&self) -> &TableViewMetrics { self.tokens.table_view_metrics() }
171    /// Returns token field pill capsule tokens.
172    pub fn token_field(&self) -> &TokenFieldMetrics { self.tokens.token_field_metrics() }
173    /// Returns level indicator VU meter tokens.
174    pub fn level_indicator(&self) -> &LevelIndicatorMetrics { self.tokens.level_indicator_metrics() }
175    /// Returns AVKit trim scrubber timeline tokens.
176    pub fn trim_scrubber(&self) -> &TrimScrubberMetrics { self.tokens.trim_scrubber_metrics() }
177    /// Returns path control breadcrumb bar tokens.
178    pub fn path_control(&self) -> &PathControlMetrics { self.tokens.path_control_metrics() }
179    /// Returns disclosure group & button tokens.
180    pub fn disclosure_group(&self) -> &DisclosureGroupMetrics { self.tokens.disclosure_group_metrics() }
181    /// Returns combo box tokens.
182    pub fn combo_box(&self) -> &ComboBoxMetrics { self.tokens.combo_box_metrics() }
183    /// Returns tab view tokens.
184    pub fn tab_view(&self) -> &TabViewMetrics { self.tokens.tab_view_metrics() }
185    /// Returns date picker tokens.
186    pub fn date_picker(&self) -> &DatePickerMetrics { self.tokens.date_picker_metrics() }
187    /// Returns collection view tokens.
188    pub fn collection_view(&self) -> &CollectionViewMetrics { self.tokens.collection_view_metrics() }
189    /// Returns player view tokens.
190    pub fn player_view(&self) -> &PlayerViewMetrics { self.tokens.player_view_metrics() }
191    /// Returns box view tokens.
192    pub fn box_view(&self) -> &BoxViewMetrics { self.tokens.box_view_metrics() }
193    /// Returns rule editor tokens.
194    pub fn rule_editor(&self) -> &RuleEditorMetrics { self.tokens.rule_editor_metrics() }
195    /// Returns square AppKit checkbox (`NSButtonTypeSwitch`) tokens.
196    pub fn checkbox(&self) -> &CheckboxMetrics { self.tokens.checkbox_metrics() }
197    /// Returns non-editable AppKit label (`NSTextField.labelWithString:`) tokens.
198    pub fn label_metrics(&self) -> &LabelMetrics { self.tokens.label_metrics() }
199    /// Returns circular AppKit help button (`NSBezelStyleHelpButton`) tokens.
200    pub fn help_button(&self) -> &HelpButtonMetrics { self.tokens.help_button_metrics() }
201    /// Returns circular inline action badge button (`16x16pt`) tokens.
202    pub fn inline_action_button(&self) -> &InlineActionButtonMetrics { self.tokens.inline_action_button_metrics() }
203    /// Returns typography and SF Pro optical tracking tokens.
204    pub fn typography(&self) -> &MacTypographyTokens { self.tokens.typography() }
205    /// Returns CoreAnimation spring physics preset tokens.
206    pub fn motion(&self) -> &MacMotionTokens { self.tokens.motion() }
207
208    /// Evaluates [`MacBackgroundStyle`] for the given background surface color.
209    pub fn background_style(&self, surface_bg: Color32) -> MacBackgroundStyle {
210        MacBackgroundStyle::from_color(surface_bg)
211    }
212    /// Evaluates [`MacBackgroundStyle`] for [`Self::control_accent`] (or custom accent override).
213    pub fn accent_background_style(&self) -> MacBackgroundStyle {
214        MacBackgroundStyle::from_color(self.control_accent())
215    }
216    /// Evaluates [`MacBackgroundStyle`] for [`Self::selected_control`].
217    pub fn selected_control_background_style(&self) -> MacBackgroundStyle {
218        MacBackgroundStyle::from_color(self.selected_control())
219    }
220    /// Returns disabled control opacity factor (`0.45`).
221    pub fn disabled_opacity(&self) -> f32 {
222        self.tokens.control_metrics().disabled_opacity.points()
223    }
224    /// Scales `color` alpha by [`Self::disabled_opacity`] when `enabled` is `false`.
225    pub fn apply_enabled(&self, color: Color32, enabled: bool) -> Color32 {
226        if enabled { color } else { color.gamma_multiply(self.disabled_opacity()) }
227    }
228
229    /// Renders an AppKit outer keyboard focus ring (`NSFocusRingPlacementOutside`) around `rect`.
230    pub fn paint_focus_ring(&self, painter: &egui::Painter, rect: egui::Rect, corner_radius: f32) {
231        let ring = self.focus_ring();
232        let focus_rect = rect.expand(ring.offset.points());
233        let focus_radius = corner_radius + ring.corner_radius_delta.points();
234        painter.rect_stroke(
235            focus_rect,
236            egui::CornerRadius::same(focus_radius as u8),
237            egui::Stroke::new(ring.stroke_width.points(), self.keyboard_focus_indicator()),
238            egui::StrokeKind::Outside,
239        );
240    }
241}