Skip to main content

geop_core_editor/
keys.rs

1//! The keys of what every front end offers besides the operations — the
2//! command palette, the key list, committing and cancelling a step,
3//! undoing, framing the view — named once, here, so that the web app and
4//! VS Code press the same keys for the same things (see `docs/ux.md`). An operation's key is its own
5//! ([`crate::editable::OperationInfo::shortcut`]).
6//!
7//! A key is written as the front ends read it: modifiers before the key,
8//! joined by `+` — `Mod` is Ctrl, or ⌘ on a Mac — `Mod+K`, `Shift+F`, `?`.
9//! Plain keys belong to the step being edited (its tools), so the view's
10//! plain keys (`F`, `Home`, `O`) work only while no step is; the key
11//! list's (`?`) and those with `Mod` always. Escape is the step's while
12//! one is edited: it ends what is in progress in it first, and with
13//! nothing in progress cancels it — or, for a sketch, keeps it, as OK does
14//! (see [`crate::editor::Editor::handle`]).
15
16use serde::Serialize;
17
18/// Something every front end offers, and its key.
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
20pub struct AppKey {
21    /// What it does, as the front ends name it: `commit`, `fit`.
22    pub action: &'static str,
23    /// Its key (see the module docs).
24    pub key: &'static str,
25    /// What it does, said as a menu says it.
26    pub label: &'static str,
27}
28
29/// The app's keys (see the module docs).
30pub const APP_KEYS: &[AppKey] = &[
31    AppKey {
32        action: "palette",
33        key: "Mod+K",
34        label: "Command palette",
35    },
36    AppKey {
37        action: "keys",
38        key: "?",
39        label: "Keyboard shortcuts",
40    },
41    AppKey {
42        action: "commit",
43        key: "Mod+Enter",
44        label: "OK: put the step into the program",
45    },
46    AppKey {
47        action: "escape",
48        key: "Escape",
49        label: "Stop what is in progress, else leave the step: a sketch kept, any other cancelled",
50    },
51    AppKey {
52        action: "cancel",
53        key: "Mod+Backspace",
54        label: "Cancel the step, whatever is in progress",
55    },
56    AppKey {
57        action: "undo",
58        key: "Mod+Z",
59        label: "Undo",
60    },
61    AppKey {
62        action: "redo",
63        key: "Mod+Shift+Z",
64        label: "Redo",
65    },
66    AppKey {
67        action: "fit",
68        key: "F",
69        label: "Fit the part in the view",
70    },
71    AppKey {
72        action: "home",
73        key: "Home",
74        label: "Reset the view",
75    },
76    AppKey {
77        action: "projection",
78        key: "O",
79        label: "Switch perspective / orthographic",
80    },
81];
82
83/// The key of `action`, if it has one.
84pub fn key_of(action: &str) -> Option<&'static str> {
85    (APP_KEYS.iter())
86        .find(|k| k.action == action)
87        .map(|k| k.key)
88}
89
90/// Whether `key` is a plain key of the app's: one no operation may take.
91pub fn is_app_key(key: &str) -> bool {
92    APP_KEYS.iter().any(|k| k.key == key)
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn every_key_is_of_one_action() {
101        for (i, a) in APP_KEYS.iter().enumerate() {
102            for b in &APP_KEYS[i + 1..] {
103                assert_ne!(a.key, b.key, "{} and {}", a.action, b.action);
104                assert_ne!(a.action, b.action);
105            }
106        }
107        assert_eq!(key_of("commit"), Some("Mod+Enter"));
108        assert!(is_app_key("F") && !is_app_key("E"));
109    }
110}