use egui::{Context, RichText, Vec2, epaint::StrokeKind};
use crate::config::KeyboardAction;
use crate::ext::UiExt;
use crate::styles::typography;
use crate::tokens::DESIGN_TOKENS;
use crate::ui_kit::dialog::{DialogFrame, dialog_header};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyboardShortcutsAction {
None,
Close,
}
pub struct KeyboardShortcutsDialog {
pub is_open: bool,
}
impl Default for KeyboardShortcutsDialog {
fn default() -> Self {
Self::new()
}
}
impl KeyboardShortcutsDialog {
pub fn new() -> Self {
Self { is_open: false }
}
pub fn open(&mut self) {
self.is_open = true;
}
pub fn close(&mut self) {
self.is_open = false;
}
pub fn toggle(&mut self) {
self.is_open = !self.is_open;
}
pub fn show(&mut self, ctx: &Context) -> KeyboardShortcutsAction {
if !self.is_open {
return KeyboardShortcutsAction::None;
}
let mut action = KeyboardShortcutsAction::None;
DialogFrame::new(
"Keyboard Shortcuts",
Vec2::new(
DESIGN_TOKENS.sizing.dialog.default_width,
DESIGN_TOKENS.sizing.dialog.default_height,
),
)
.show(ctx, |ui| {
action = self.render_contents(ui);
});
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
self.is_open = false;
action = KeyboardShortcutsAction::Close;
}
action
}
fn render_contents(&mut self, ui: &mut egui::Ui) -> KeyboardShortcutsAction {
let mut action = KeyboardShortcutsAction::None;
if dialog_header(ui, "Keyboard Shortcuts") {
action = KeyboardShortcutsAction::Close;
self.is_open = false;
}
ui.separator();
egui::ScrollArea::vertical()
.max_height(DESIGN_TOKENS.sizing.dialog.default_height - 80.0)
.show(ui, |ui| {
ui.space_md();
let categories = KeyboardAction::all_by_category();
for (idx, (category_name, actions)) in categories.iter().enumerate() {
if idx > 0 {
ui.space_md();
}
self.render_category(ui, category_name, actions);
}
ui.space_lg();
});
action
}
fn render_category(&self, ui: &mut egui::Ui, name: &str, actions: &[KeyboardAction]) {
ui.horizontal(|ui| {
ui.space_lg();
ui.label(
RichText::new(name)
.size(typography::LG)
.strong()
.color(ui.style().visuals.text_color()),
);
});
ui.space_xs();
egui::Grid::new(format!("shortcuts_{name}"))
.num_columns(2)
.spacing([DESIGN_TOKENS.spacing.xl, DESIGN_TOKENS.spacing.sm])
.min_col_width(DESIGN_TOKENS.sizing.settings_dialog.label_width)
.show(ui, |ui| {
for action in actions {
ui.horizontal(|ui| {
ui.space_xl();
ui.label(
RichText::new(action.label())
.size(typography::MD)
.color(ui.style().visuals.text_color()),
);
});
ui.horizontal(|ui| {
self.render_key_badge(ui, action.shortcut_key());
});
ui.end_row();
}
});
}
fn render_key_badge(&self, ui: &mut egui::Ui, key_text: &str) {
let alternatives: Vec<&str> = key_text.split(" / ").collect();
for (alt_idx, alt) in alternatives.iter().enumerate() {
if alt_idx > 0 {
ui.label(
RichText::new("/")
.size(typography::SM)
.color(ui.style().visuals.weak_text_color()),
);
}
let parts: Vec<&str> = alt.split('+').collect();
for (part_idx, part) in parts.iter().enumerate() {
if part_idx > 0 {
ui.label(
RichText::new("+")
.size(typography::SM)
.color(ui.style().visuals.weak_text_color()),
);
}
let badge_text = RichText::new(*part)
.size(typography::SM)
.color(ui.style().visuals.text_color());
let badge = egui::Label::new(badge_text);
let response = ui.add(badge);
let badge_rect = response.rect.expand(DESIGN_TOKENS.spacing.xs);
let badge_rounding = DESIGN_TOKENS.rounding.sm;
let badge_bg = ui.style().visuals.widgets.inactive.bg_fill;
let badge_stroke = ui.style().visuals.widgets.inactive.bg_stroke;
ui.painter().rect(
badge_rect,
badge_rounding,
badge_bg,
badge_stroke,
StrokeKind::Inside,
);
}
}
}
}