Skip to main content

launcher/widgets/
nav_button.rs

1use eframe::egui;
2
3/// Creates a pill-style navigation button with a circular icon
4/// background, styled after the web pages' chip vocabulary: the selected
5/// entry is accent-filled, a hovered entry wears the chip tone, and the
6/// warning dot uses the stylesheet's error color.
7///
8/// # Arguments
9/// * `ui` - The egui UI context
10/// * `icon` - The emoji or icon to display in the circle
11/// * `label` - The text label for the button
12/// * `is_selected` - Whether this button represents the currently selected page
13/// * `has_warning` - Whether to draw the small warning dot on the icon
14///
15/// # Returns
16/// The response from the button interaction
17pub fn nav_button(
18    ui: &mut egui::Ui,
19    icon: &str,
20    label: &str,
21    is_selected: bool,
22    has_warning: bool,
23) -> egui::Response {
24    let palette = crate::theme::palette(ui.ctx());
25    let desired_size = egui::vec2(ui.available_width(), 44.0);
26    let (rect, response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
27
28    if ui.is_rect_visible(rect) {
29        let (bg_color, icon_bg_color, text_color) = if is_selected {
30            (
31                palette.accent,
32                egui::Color32::from_white_alpha(46),
33                palette.accent_ink,
34            )
35        } else if response.hovered() {
36            (palette.chip, palette.chip, palette.chip_ink)
37        } else {
38            (egui::Color32::TRANSPARENT, palette.chip, palette.text)
39        };
40
41        // Pill-shaped background, the stylesheet's 999 px corner chips.
42        ui.painter()
43            .rect_filled(rect, 22.0, bg_color);
44
45        // Circular icon background on the left
46        let icon_circle_radius = 15.0;
47        let icon_circle_center = egui::pos2(rect.left() + 23.0, rect.center().y);
48        ui.painter()
49            .circle_filled(icon_circle_center, icon_circle_radius, icon_bg_color);
50
51        // Draw icon in the circle
52        let icon_galley = ui.painter().layout_no_wrap(
53            icon.to_string(),
54            egui::FontId::proportional(16.0),
55            text_color,
56        );
57        let icon_pos = egui::pos2(
58            icon_circle_center.x - icon_galley.size().x / 2.0,
59            icon_circle_center.y - icon_galley.size().y / 2.0,
60        );
61        ui.painter().galley(icon_pos, icon_galley, text_color);
62
63        // Draw warning badge if needed
64        if has_warning {
65            let badge_radius = 4.0;
66            let badge_center = egui::pos2(
67                icon_circle_center.x + icon_circle_radius * 0.7,
68                icon_circle_center.y - icon_circle_radius * 0.7,
69            );
70            ui.painter()
71                .circle_filled(badge_center, badge_radius, palette.err);
72        }
73
74        // Draw label text
75        let text_galley = ui.painter().layout_no_wrap(
76            label.to_string(),
77            egui::FontId::proportional(14.0),
78            text_color,
79        );
80        let text_pos = egui::pos2(
81            rect.left() + 50.0,
82            rect.center().y - text_galley.size().y / 2.0,
83        );
84        ui.painter().galley(text_pos, text_galley, text_color);
85
86        // Focus ring if button has focus
87        if response.has_focus() {
88            ui.painter().rect_stroke(
89                rect,
90                22.0,
91                egui::Stroke::new(1.0, palette.accent),
92                egui::StrokeKind::Inside,
93            );
94        }
95    }
96
97    response
98}