Skip to main content

armas_basic/components/
kbd.rs

1//! Kbd Component (shadcn/ui style)
2//!
3//! Keyboard shortcut display element.
4
5use crate::ext::ArmasContextExt;
6use crate::theme::Theme;
7use egui::{Response, Ui, Vec2};
8
9/// Keyboard shortcut display component
10///
11/// # Example
12///
13/// ```rust,no_run
14/// # use egui::Ui;
15/// # fn example(ui: &mut Ui) {
16/// use armas_basic::Kbd;
17///
18/// // Single key
19/// Kbd::new("K").show(ui);
20///
21/// // Key combination (auto-splits on +)
22/// Kbd::new("Ctrl+K").show(ui);
23/// # }
24/// ```
25pub struct Kbd {
26    text: String,
27}
28
29impl Kbd {
30    /// Create a new Kbd with the given key text
31    pub fn new(text: impl Into<String>) -> Self {
32        Self { text: text.into() }
33    }
34
35    /// Show the keyboard shortcut
36    pub fn show(self, ui: &mut Ui) -> Response {
37        let theme = ui.ctx().armas_theme();
38        // Check if this is a key combination
39        let parts: Vec<&str> = self.text.split('+').map(str::trim).collect();
40
41        if parts.len() > 1 {
42            // Multiple keys - render as group
43            let response = ui.horizontal(|ui| {
44                ui.spacing_mut().item_spacing.x = 2.0;
45                for (i, part) in parts.iter().enumerate() {
46                    if i > 0 {
47                        ui.label("+");
48                    }
49                    render_key(ui, part, &theme);
50                }
51            });
52            response.response
53        } else {
54            // Single key
55            render_key(ui, &self.text, &theme)
56        }
57    }
58}
59
60fn render_key(ui: &mut Ui, text: &str, theme: &Theme) -> Response {
61    let font_size = theme.typography.sm;
62    let font_id = egui::FontId::proportional(font_size);
63    let text_color = theme.muted_foreground();
64    let bg_color = theme.muted();
65
66    // Calculate text size
67    let galley = ui
68        .painter()
69        .layout_no_wrap(text.to_string(), font_id, text_color);
70
71    let text_size = galley.size();
72    let padding_x = 8.0;
73    let padding_y = 4.0;
74    let min_width = 24.0;
75    let height = font_size + padding_y * 2.0;
76
77    let size = Vec2::new((text_size.x + padding_x * 2.0).max(min_width), height);
78
79    let (rect, response) = ui.allocate_exact_size(size, egui::Sense::hover());
80
81    if ui.is_rect_visible(rect) {
82        let rounding = 4.0;
83
84        // Background
85        ui.painter().rect_filled(rect, rounding, bg_color);
86
87        // Border for depth
88        ui.painter().rect_stroke(
89            rect,
90            rounding,
91            egui::Stroke::new(1.0, theme.border()),
92            egui::StrokeKind::Inside,
93        );
94
95        // Text centered
96        ui.painter()
97            .galley(rect.center() - text_size / 2.0, galley, text_color);
98    }
99
100    response
101}