feat(input): add editable shortcut table

Persist all active bindings and route keyboard events through the configured table.\n\nRefs #449
This commit is contained in:
Hakan Seven committed 2026-08-04 15:35:06 +03:00
1 parent 3f6959d7d0
commit 2f86bfde8e
9 files changed
+448 -337

No files matched your search

+8
View File
@@ -530,6 +530,13 @@ impl OpenCADStudio {
collapse: self.ribbon.collapse_mode(),
},
plot: self.plot_dialog.clone(),
shortcuts: crate::app::config::ShortcutConfig {
bindings: self
.shortcut_bindings
.iter()
.map(|(key, command)| (key.clone(), command.clone()))
.collect(),
},
}
}
@@ -565,6 +572,7 @@ impl OpenCADStudio {
self.annotation_auto_scale = cfg.annotation_auto_scale.clamp(-4, 4);
self.ribbon.set_collapse_mode(cfg.ribbon.collapse);
self.plot_dialog = cfg.plot;
self.shortcut_bindings = cfg.shortcuts.bindings.into_iter().collect();
}
/// Write the config only when it changed since the last write, so a toggle
+42 -4
View File
@@ -29,6 +29,7 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
| Message::TogglePolar
| Message::ToggleOTrack
| Message::ToggleDynInput
| Message::ShortcutPressed(_)
| Message::TabNew
| Message::OpenFile
| Message::SaveFile
@@ -171,9 +172,8 @@ impl OpenCADStudio {
}
// Closing (✕) discards edits made since the last Apply — matching the
// style editors. Committing happens only through the Apply button.
Some(Aliases) => {
self.alias_editor_rows.clear();
}
Some(Aliases) => self.alias_editor_rows.clear(),
Some(Shortcuts) => self.shortcut_editor_rows.clear(),
Some(LayerStateEditor) => {
self.layer_state_edit_draft = None;
self.layer_state_edit_filter.clear();
@@ -206,7 +206,9 @@ impl OpenCADStudio {
// the modal's own text fields keep working because they emit their own
// (non-blocked) messages. (#126)
if self.active_modal.is_some() {
if matches!(msg, Message::CommandEscape) {
if matches!(msg, Message::CommandEscape)
|| matches!(&msg, Message::ShortcutPressed(key) if key.rsplit('+').next() == Some("ESCAPE"))
{
return self.update(Message::CloseModal);
}
if is_modal_blocked_key_msg(&msg) {
@@ -4475,6 +4477,13 @@ impl OpenCADStudio {
// ── Keyboard Shortcuts Panel ──────────────────────────────────────
Message::ShortcutsPanelOpen => {
let mut rows: Vec<(String, String)> = self
.shortcut_bindings
.iter()
.map(|(key, command)| (key.clone(), command.clone()))
.collect();
rows.sort_by(|a, b| a.0.cmp(&b.0));
self.shortcut_editor_rows = rows;
self.active_modal = Some(super::ModalKind::Shortcuts);
Task::none()
}
@@ -4482,6 +4491,35 @@ impl OpenCADStudio {
self.close_active_modal();
Task::none()
}
Message::ShortcutEditorInput { idx, field, value } => {
use crate::ui::window::shortcuts::ShortcutField;
if let Some(row) = self.shortcut_editor_rows.get_mut(idx) {
match field {
ShortcutField::Key => row.0 = value.to_uppercase(),
ShortcutField::Command => row.1 = value.to_uppercase(),
}
}
Task::none()
}
Message::ShortcutEditorAdd => {
self.shortcut_editor_rows
.push((String::new(), String::new()));
Task::none()
}
Message::ShortcutEditorRemove(idx) => {
if idx < self.shortcut_editor_rows.len() {
self.shortcut_editor_rows.remove(idx);
}
Task::none()
}
Message::ShortcutEditorApply => {
self.apply_shortcut_editor_rows();
self.command_line.push_info(
crate::tf!("{} shortcut(s) applied.", self.shortcut_bindings.len()).as_ref(),
);
Task::none()
}
Message::ShortcutPressed(key) => self.run_shortcut(&key),
// ── Command Alias Editor (ALIASEDIT) ──────────────────────────────
Message::AliasEditorOpen => {