1use crate::controls::MacControlSize;
11use crate::layout::{subpixel_snap, 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 surface_bg = theme.active_surface(ui);
185 let text_color = self.resolved_color_on(theme, surface_bg);
186 let is_heavy = matches!(
187 self.weight,
188 MacFontWeight::Medium | MacFontWeight::Semibold | MacFontWeight::Bold
189 );
190 let (font_id, has_semibold_family) = if is_heavy {
191 theme.semibold_font_id(ui, font_sz)
192 } else {
193 (FontId::proportional(font_sz), false)
194 };
195
196 let wrap_w = self.fixed_rect.map_or(f32::INFINITY, |r| r.width());
197 let galley =
198 theme.layout_tracked_galley(ui, self.text.text(), font_id, text_color, wrap_w);
199
200 let ppp = ui.ctx().pixels_per_point();
201 let (rect, response) = if let Some(fixed) = self.fixed_rect {
202 let snapped = subpixel_snap_rect(fixed, ppp);
203 let id = ui.next_auto_id().with("mac_label_fixed");
204 let resp = ui.interact(snapped, id, Sense::hover());
205 (snapped, resp)
206 } else {
207 let desired = Vec2::new(galley.size().x, cell_h.max(galley.size().y));
208 let (r, resp) = ui.allocate_exact_size(desired, Sense::hover());
209 (subpixel_snap_rect(r, ppp), resp)
210 };
211
212 let painter = ui.painter_at(rect);
213 let text_y = subpixel_snap(rect.top() + (baseline_from_top - ascender), ppp);
214 let text_x = match self.alignment {
215 Align::Min => rect.left(),
216 Align::Center => rect.center().x - galley.size().x * 0.5,
217 Align::Max => (rect.right() - galley.size().x).max(rect.left()),
218 };
219 let pos = Pos2::new(text_x, text_y);
220 painter.galley(pos, galley.clone(), text_color);
221
222 if is_heavy && !has_semibold_family {
223 let boost = match self.weight {
224 MacFontWeight::Bold => lm.bold_stroke_boost.points(),
225 MacFontWeight::Medium | MacFontWeight::Semibold => lm.semibold_stroke_boost.points(),
226 MacFontWeight::Regular => 0.0,
227 };
228 if boost > 0.0 {
229 painter.galley(Pos2::new(pos.x + boost, pos.y), galley, text_color);
230 }
231 }
232
233 response
234 }
235}