Skip to main content

elegance/
badge.rs

1//! Small rounded badges — used for status labels like "OK", "Connected",
2//! "Pending", "Error".
3
4use egui::{
5    Color32, CornerRadius, FontSelection, Response, Sense, Stroke, Ui, Vec2, Widget, WidgetInfo,
6    WidgetText, WidgetType,
7};
8
9use crate::theme::{with_alpha, Theme};
10
11/// Colour tones for a [`Badge`].
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13pub enum BadgeTone {
14    /// Success — green.
15    Ok,
16    /// Caution — amber.
17    Warning,
18    /// Error — red.
19    Danger,
20    /// Informational — sky.
21    Info,
22    /// Neutral grey, for status that isn't success/warning/error.
23    Neutral,
24}
25
26impl BadgeTone {
27    fn colours(self, theme: &Theme) -> (Color32, Color32) {
28        let p = &theme.palette;
29        match self {
30            BadgeTone::Ok => (with_alpha(p.green, 64), p.success),
31            BadgeTone::Warning => (with_alpha(p.amber, 64), p.warning),
32            BadgeTone::Danger => (with_alpha(p.red, 64), p.danger),
33            BadgeTone::Info => (with_alpha(p.sky, 64), p.sky),
34            BadgeTone::Neutral => (with_alpha(p.text_muted, 40), p.text_muted),
35        }
36    }
37}
38
39/// A compact rounded status badge.
40///
41/// ```no_run
42/// # use elegance::{Badge, BadgeTone};
43/// # egui::__run_test_ui(|ui| {
44/// ui.add(Badge::new("OK", BadgeTone::Ok));
45/// # });
46/// ```
47#[must_use = "Add with `ui.add(...)`."]
48pub struct Badge {
49    text: WidgetText,
50    tone: BadgeTone,
51}
52
53impl std::fmt::Debug for Badge {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        f.debug_struct("Badge")
56            .field("text", &self.text.text())
57            .field("tone", &self.tone)
58            .finish()
59    }
60}
61
62impl Badge {
63    /// Create a badge with the given text and tone.
64    pub fn new(text: impl Into<WidgetText>, tone: BadgeTone) -> Self {
65        Self {
66            text: text.into(),
67            tone,
68        }
69    }
70}
71
72impl Widget for Badge {
73    fn ui(self, ui: &mut Ui) -> Response {
74        let theme = Theme::current(ui.ctx());
75        let t = &theme.typography;
76        let (bg, fg) = self.tone.colours(&theme);
77
78        let font = egui::FontId::proportional(t.small);
79        let galley = egui::WidgetText::from(
80            egui::RichText::new(self.text.text().to_uppercase())
81                .color(fg)
82                .size(t.small)
83                .strong(),
84        )
85        .into_galley(
86            ui,
87            Some(egui::TextWrapMode::Extend),
88            f32::INFINITY,
89            FontSelection::FontId(font),
90        );
91
92        let pad = Vec2::new(9.0, 3.0);
93        let desired = galley.size() + pad * 2.0;
94        let (rect, response) = ui.allocate_exact_size(desired, Sense::hover());
95
96        if ui.is_rect_visible(rect) {
97            ui.painter().rect(
98                rect,
99                CornerRadius::same(99),
100                bg,
101                Stroke::NONE,
102                egui::StrokeKind::Inside,
103            );
104            let text_pos = egui::pos2(rect.min.x + pad.x, rect.center().y - galley.size().y * 0.5);
105            ui.painter().galley(text_pos, galley, fg);
106        }
107
108        response.widget_info(|| WidgetInfo::labeled(WidgetType::Label, true, self.text.text()));
109        response
110    }
111}