use crate::controls::MacControlSize;
use crate::layout::{subpixel_snap, subpixel_snap_rect};
use crate::theme::MacTheme;
use egui::{Align, Color32, FontId, Pos2, Rect, Response, Sense, Ui, Vec2, Widget, WidgetText};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MacFontWeight {
#[default]
Regular,
Medium,
Semibold,
Bold,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MacLabelColor {
#[default]
Primary,
Secondary,
Tertiary,
}
pub struct MacLabel<'a> {
text: WidgetText,
control_size: MacControlSize,
weight: MacFontWeight,
color: MacLabelColor,
alignment: Align,
fixed_rect: Option<Rect>,
theme: Option<&'a MacTheme>,
}
impl<'a> MacLabel<'a> {
pub fn new(text: impl Into<WidgetText>) -> Self {
Self {
text: text.into(),
control_size: MacControlSize::Regular,
weight: MacFontWeight::Regular,
color: MacLabelColor::Primary,
alignment: Align::Min,
fixed_rect: None,
theme: None,
}
}
pub fn control_size(mut self, size: MacControlSize) -> Self {
self.control_size = size;
self
}
pub fn weight(mut self, weight: MacFontWeight) -> Self {
self.weight = weight;
self
}
pub fn color(mut self, color: MacLabelColor) -> Self {
self.color = color;
self
}
pub fn alignment(mut self, alignment: Align) -> Self {
self.alignment = alignment;
self
}
pub fn fixed_rect(mut self, rect: Rect) -> Self {
self.fixed_rect = Some(rect);
self
}
pub fn theme(mut self, theme: &'a MacTheme) -> Self {
self.theme = Some(theme);
self
}
pub fn font_size(&self) -> f32 {
let theme_cow = MacTheme::resolve_or_default(self.theme);
let lm = theme_cow.label_metrics();
match self.control_size {
MacControlSize::Regular | MacControlSize::Large => lm.regular_font_size.points(),
MacControlSize::Small | MacControlSize::CompactInfobox => lm.small_font_size.points(),
MacControlSize::Mini => lm.mini_font_size.points(),
}
}
pub fn resolved_color(&self, theme: &MacTheme) -> Color32 {
match self.color {
MacLabelColor::Primary => theme.label(),
MacLabelColor::Secondary => theme.secondary_label(),
MacLabelColor::Tertiary => theme.tertiary_label(),
}
}
pub fn resolved_color_on(&self, theme: &MacTheme, surface_bg: Color32) -> Color32 {
match self.color {
MacLabelColor::Primary => theme.label_on(surface_bg),
MacLabelColor::Secondary => theme.secondary_label_on(surface_bg),
MacLabelColor::Tertiary => theme.tertiary_label_on(surface_bg),
}
}
pub fn show(self, ui: &mut Ui) -> Response {
ui.add(self)
}
}
impl<'a> Widget for MacLabel<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
let theme: &MacTheme = &theme_cow;
let lm = theme.label_metrics();
let (font_sz, cell_h, baseline_from_top, ascender) = match self.control_size {
MacControlSize::Regular | MacControlSize::Large => (
lm.regular_font_size.points(),
lm.regular_height.points(),
lm.regular_baseline_from_top.points(),
lm.regular_ascender.points(),
),
MacControlSize::Small | MacControlSize::CompactInfobox => (
lm.small_font_size.points(),
lm.small_height.points(),
lm.small_baseline_from_top.points(),
lm.small_ascender.points(),
),
MacControlSize::Mini => (
lm.mini_font_size.points(),
lm.mini_height.points(),
lm.mini_baseline_from_top.points(),
lm.mini_ascender.points(),
),
};
let surface_bg = theme.active_surface(ui);
let text_color = self.resolved_color_on(theme, surface_bg);
let is_heavy = matches!(
self.weight,
MacFontWeight::Medium | MacFontWeight::Semibold | MacFontWeight::Bold
);
let (font_id, has_semibold_family) = if is_heavy {
theme.semibold_font_id(ui, font_sz)
} else {
(FontId::proportional(font_sz), false)
};
let wrap_w = self.fixed_rect.map_or(f32::INFINITY, |r| r.width());
let galley =
theme.layout_tracked_galley(ui, self.text.text(), font_id, text_color, wrap_w);
let ppp = ui.ctx().pixels_per_point();
let (rect, response) = if let Some(fixed) = self.fixed_rect {
let snapped = subpixel_snap_rect(fixed, ppp);
let id = ui.next_auto_id().with("mac_label_fixed");
let resp = ui.interact(snapped, id, Sense::hover());
(snapped, resp)
} else {
let desired = Vec2::new(galley.size().x, cell_h.max(galley.size().y));
let (r, resp) = ui.allocate_exact_size(desired, Sense::hover());
(subpixel_snap_rect(r, ppp), resp)
};
let painter = ui.painter_at(rect);
let text_y = subpixel_snap(rect.top() + (baseline_from_top - ascender), ppp);
let text_x = match self.alignment {
Align::Min => rect.left(),
Align::Center => rect.center().x - galley.size().x * 0.5,
Align::Max => (rect.right() - galley.size().x).max(rect.left()),
};
let pos = Pos2::new(text_x, text_y);
painter.galley(pos, galley.clone(), text_color);
if is_heavy && !has_semibold_family {
let boost = match self.weight {
MacFontWeight::Bold => lm.bold_stroke_boost.points(),
MacFontWeight::Medium | MacFontWeight::Semibold => lm.semibold_stroke_boost.points(),
MacFontWeight::Regular => 0.0,
};
if boost > 0.0 {
painter.galley(Pos2::new(pos.x + boost, pos.y), galley, text_color);
}
}
response
}
}