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 show(self, ui: &mut Ui) -> Response {
144 ui.add(self)
145 }
146}
147
148impl<'a> Widget for MacLabel<'a> {
149 fn ui(self, ui: &mut Ui) -> Response {
150 let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
151 let theme: &MacTheme = &theme_cow;
152 let lm = theme.label_metrics();
153
154 let (font_sz, cell_h, baseline_from_top, ascender) = match self.control_size {
155 MacControlSize::Regular | MacControlSize::Large => (
156 lm.regular_font_size.points(),
157 lm.regular_height.points(),
158 lm.regular_baseline_from_top.points(),
159 lm.regular_ascender.points(),
160 ),
161 MacControlSize::Small | MacControlSize::CompactInfobox => (
162 lm.small_font_size.points(),
163 lm.small_height.points(),
164 lm.small_baseline_from_top.points(),
165 lm.small_ascender.points(),
166 ),
167 MacControlSize::Mini => (
168 lm.mini_font_size.points(),
169 lm.mini_height.points(),
170 lm.mini_baseline_from_top.points(),
171 lm.mini_ascender.points(),
172 ),
173 };
174
175 let text_color = self.resolved_color(theme);
176 let is_heavy = matches!(
177 self.weight,
178 MacFontWeight::Medium | MacFontWeight::Semibold | MacFontWeight::Bold
179 );
180 let (font_id, has_semibold_family) = if is_heavy {
181 theme.semibold_font_id(ui, font_sz)
182 } else {
183 (FontId::proportional(font_sz), false)
184 };
185
186 let wrap_w = self.fixed_rect.map_or(f32::INFINITY, |r| r.width());
187 let galley =
188 theme.layout_tracked_galley(ui, self.text.text(), font_id, text_color, wrap_w);
189
190 let ppp = ui.ctx().pixels_per_point();
191 let (rect, response) = if let Some(fixed) = self.fixed_rect {
192 let snapped = subpixel_snap_rect(fixed, ppp);
193 let id = ui.next_auto_id().with("mac_label_fixed");
194 let resp = ui.interact(snapped, id, Sense::hover());
195 (snapped, resp)
196 } else {
197 let desired = Vec2::new(galley.size().x, cell_h.max(galley.size().y));
198 let (r, resp) = ui.allocate_exact_size(desired, Sense::hover());
199 (subpixel_snap_rect(r, ppp), resp)
200 };
201
202 let painter = ui.painter_at(rect);
203 let text_y = subpixel_snap(rect.top() + (baseline_from_top - ascender), ppp);
204 let text_x = match self.alignment {
205 Align::Min => rect.left(),
206 Align::Center => rect.center().x - galley.size().x * 0.5,
207 Align::Max => (rect.right() - galley.size().x).max(rect.left()),
208 };
209 let pos = Pos2::new(text_x, text_y);
210 painter.galley(pos, galley.clone(), text_color);
211
212 if is_heavy && !has_semibold_family {
213 let boost = match self.weight {
214 MacFontWeight::Bold => lm.bold_stroke_boost.points(),
215 MacFontWeight::Medium | MacFontWeight::Semibold => lm.semibold_stroke_boost.points(),
216 MacFontWeight::Regular => 0.0,
217 };
218 if boost > 0.0 {
219 painter.galley(Pos2::new(pos.x + boost, pos.y), galley, text_color);
220 }
221 }
222
223 response
224 }
225}