1use crate::controls::MacControlSize;
11use crate::layout::subpixel_snap_rect;
12use crate::theme::MacTheme;
13use egui::{Align, Color32, FontId, Pos2, Rect, Response, Sense, Ui, Vec2, Widget, WidgetText};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum MacFontWeight {
18 #[default]
20 Regular,
21 Medium,
23 Semibold,
25 Bold,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
31pub enum MacLabelColor {
32 #[default]
34 Primary,
35 Secondary,
37 Tertiary,
39}
40
41pub struct MacLabel<'a> {
63 text: WidgetText,
64 control_size: MacControlSize,
65 weight: MacFontWeight,
66 color: MacLabelColor,
67 alignment: Align,
68 fixed_rect: Option<Rect>,
69 theme: Option<&'a MacTheme>,
70}
71
72impl<'a> MacLabel<'a> {
73 pub fn new(text: impl Into<WidgetText>) -> Self {
75 Self {
76 text: text.into(),
77 control_size: MacControlSize::Regular,
78 weight: MacFontWeight::Regular,
79 color: MacLabelColor::Primary,
80 alignment: Align::Min,
81 fixed_rect: None,
82 theme: None,
83 }
84 }
85
86 pub fn control_size(mut self, size: MacControlSize) -> Self {
88 self.control_size = size;
89 self
90 }
91
92 pub fn weight(mut self, weight: MacFontWeight) -> Self {
94 self.weight = weight;
95 self
96 }
97
98 pub fn color(mut self, color: MacLabelColor) -> Self {
100 self.color = color;
101 self
102 }
103
104 pub fn alignment(mut self, alignment: Align) -> Self {
106 self.alignment = alignment;
107 self
108 }
109
110 pub fn fixed_rect(mut self, rect: Rect) -> Self {
112 self.fixed_rect = Some(rect);
113 self
114 }
115
116 pub fn theme(mut self, theme: &'a MacTheme) -> Self {
118 self.theme = Some(theme);
119 self
120 }
121
122 pub fn font_size(&self) -> f32 {
124 let theme_cow = MacTheme::resolve_or_default(self.theme);
125 let lm = theme_cow.label_metrics();
126 match self.control_size {
127 MacControlSize::Regular | MacControlSize::Large => lm.regular_font_size.points(),
128 MacControlSize::Small | MacControlSize::CompactInfobox => lm.small_font_size.points(),
129 MacControlSize::Mini => lm.mini_font_size.points(),
130 }
131 }
132
133 pub fn resolved_color(&self, theme: &MacTheme) -> Color32 {
135 match self.color {
136 MacLabelColor::Primary => theme.label(),
137 MacLabelColor::Secondary => theme.secondary_label(),
138 MacLabelColor::Tertiary => theme.tertiary_label(),
139 }
140 }
141
142 pub fn resolved_color_on(&self, theme: &MacTheme, surface_bg: Color32) -> Color32 {
144 match self.color {
145 MacLabelColor::Primary => theme.label_on(surface_bg),
146 MacLabelColor::Secondary => theme.secondary_label_on(surface_bg),
147 MacLabelColor::Tertiary => theme.tertiary_label_on(surface_bg),
148 }
149 }
150
151 pub fn show(self, ui: &mut Ui) -> Response {
153 ui.add(self)
154 }
155}
156
157impl<'a> Widget for MacLabel<'a> {
158 fn ui(self, ui: &mut Ui) -> Response {
159 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
160 let theme: &MacTheme = &theme_cow;
161 let lm = theme.label_metrics();
162
163 let (font_sz, cell_h, baseline_from_top, ascender) = match self.control_size {
164 MacControlSize::Regular | MacControlSize::Large => (
165 lm.regular_font_size.points(),
166 lm.regular_height.points(),
167 lm.regular_baseline_from_top.points(),
168 lm.regular_ascender.points(),
169 ),
170 MacControlSize::Small | MacControlSize::CompactInfobox => (
171 lm.small_font_size.points(),
172 lm.small_height.points(),
173 lm.small_baseline_from_top.points(),
174 lm.small_ascender.points(),
175 ),
176 MacControlSize::Mini => (
177 lm.mini_font_size.points(),
178 lm.mini_height.points(),
179 lm.mini_baseline_from_top.points(),
180 lm.mini_ascender.points(),
181 ),
182 };
183
184 let probe_pos = self.fixed_rect.map_or_else(
185 || ui.next_widget_position(),
186 |r| match self.alignment {
187 Align::Min => Pos2::new(r.left(), r.center().y),
188 Align::Center => r.center(),
189 Align::Max => Pos2::new(r.right(), r.center().y),
190 },
191 );
192 let surface_bg = theme.active_surface_at(ui, probe_pos);
193 let text_color = theme.apply_enabled(self.resolved_color_on(theme, surface_bg), ui.is_enabled());
194 let is_heavy = matches!(
195 self.weight,
196 MacFontWeight::Medium | MacFontWeight::Semibold | MacFontWeight::Bold
197 );
198 let (font_id, has_semibold_family) = if is_heavy {
199 theme.semibold_font_id(ui, font_sz)
200 } else {
201 (FontId::proportional(font_sz), false)
202 };
203
204 let wrap_w = self.fixed_rect.map_or(f32::INFINITY, |r| r.width());
205 let galley =
206 theme.layout_tracked_galley(ui, self.text.text(), font_id, text_color, wrap_w);
207
208 let ppp = ui.ctx().pixels_per_point();
209 let (rect, response) = if let Some(fixed) = self.fixed_rect {
210 let snapped = subpixel_snap_rect(fixed, ppp);
211 let id = ui.next_auto_id().with("mac_label_fixed");
212 ui.skip_ahead_auto_ids(1);
213 let resp = ui.interact(snapped, id, Sense::hover());
214 (snapped, resp)
215 } else {
216 let desired = Vec2::new(galley.size().x, cell_h.max(galley.size().y));
217 let (r, resp) = ui.allocate_exact_size(desired, Sense::hover());
218 (subpixel_snap_rect(r, ppp), resp)
219 };
220
221 let painter = ui.painter_at(rect);
222 let text_y = theme.baseline_text_y(rect, baseline_from_top, ascender, ppp);
223 let text_x = match self.alignment {
224 Align::Min => rect.left(),
225 Align::Center => rect.center().x - galley.size().x * 0.5,
226 Align::Max => (rect.right() - galley.size().x).max(rect.left()),
227 };
228 let pos = Pos2::new(text_x, text_y);
229 painter.galley(pos, galley.clone(), text_color);
230
231 if is_heavy && !has_semibold_family {
232 let boost = match self.weight {
233 MacFontWeight::Bold => lm.bold_stroke_boost.points(),
234 MacFontWeight::Medium | MacFontWeight::Semibold => lm.semibold_stroke_boost.points(),
235 MacFontWeight::Regular => 0.0,
236 };
237 if boost > 0.0 {
238 painter.galley(Pos2::new(pos.x + boost, pos.y), galley, text_color);
239 }
240 }
241
242 response
243 }
244}