armas_basic/components/
kbd.rs1use crate::ext::ArmasContextExt;
6use crate::theme::Theme;
7use egui::{Response, Ui, Vec2};
8
9pub struct Kbd {
26 text: String,
27}
28
29impl Kbd {
30 pub fn new(text: impl Into<String>) -> Self {
32 Self { text: text.into() }
33 }
34
35 pub fn show(self, ui: &mut Ui) -> Response {
37 let theme = ui.ctx().armas_theme();
38 let parts: Vec<&str> = self.text.split('+').map(str::trim).collect();
40
41 if parts.len() > 1 {
42 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 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 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 ui.painter().rect_filled(rect, rounding, bg_color);
86
87 ui.painter().rect_stroke(
89 rect,
90 rounding,
91 egui::Stroke::new(1.0, theme.border()),
92 egui::StrokeKind::Inside,
93 );
94
95 ui.painter()
97 .galley(rect.center() - text_size / 2.0, galley, text_color);
98 }
99
100 response
101}