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