1use crate::effects::{Dispose, on_unmount};
2use crate::input::{Key, Modifiers};
3use std::cell::RefCell;
4use std::rc::Rc;
5
6#[derive(Clone, Debug, PartialEq)]
7pub enum Gesture {
8 SwipeLeft,
9 SwipeRight,
10 Pinch {
12 delta_scale: f32,
13 },
14}
15
16#[derive(Clone, Debug, PartialEq)]
17pub enum Action {
18 Copy,
19 Cut,
20 Paste,
21 SelectAll,
22 Undo,
23 Redo,
24
25 Back,
26 Find,
27 Save,
28
29 Gesture(Gesture),
30 Custom(Rc<str>),
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub struct KeyChord {
35 pub key: Key,
36 pub modifiers: Modifiers,
37}
38
39impl KeyChord {
40 pub fn new(key: Key, modifiers: Modifiers) -> Self {
41 Self { key, modifiers }
42 }
43}
44
45#[derive(Clone, Debug)]
46pub struct ShortcutBinding {
47 pub chord: KeyChord,
48 pub action: Action,
49}
50
51#[derive(Clone, Debug, Default)]
52pub struct ShortcutMap {
53 pub bindings: Vec<ShortcutBinding>,
54}
55
56impl ShortcutMap {
57 pub fn new() -> Self {
58 Self {
59 bindings: Vec::new(),
60 }
61 }
62
63 pub fn bind(mut self, key: Key, modifiers: Modifiers, action: Action) -> Self {
64 self.bindings.push(ShortcutBinding {
65 chord: KeyChord::new(key, modifiers),
66 action,
67 });
68 self
69 }
70
71 pub fn bind_action(mut self, action: Action) -> Self {
72 if let Some(chord) = default_chord_for(&action) {
73 self.bindings.push(ShortcutBinding { chord, action });
74 }
75 self
76 }
77
78 pub fn merge(mut self, other: ShortcutMap) -> Self {
79 self.bindings.extend(other.bindings);
80 self
81 }
82
83 pub fn insert(&mut self, key: Key, modifiers: Modifiers, action: Action) {
84 self.bindings.push(ShortcutBinding {
85 chord: KeyChord::new(key, modifiers),
86 action,
87 });
88 }
89
90 pub fn action_for(&self, chord: &KeyChord) -> Option<Action> {
91 self.bindings
92 .iter()
93 .rev()
94 .find(|binding| &binding.chord == chord)
95 .map(|binding| binding.action.clone())
96 }
97}
98
99pub type Handler = Rc<dyn Fn(Action) -> bool>;
100
101thread_local! {
102 static HANDLER: RefCell<Option<Handler>> = RefCell::new(None);
103 static DEFAULT_MAP: RefCell<ShortcutMap> = RefCell::new(default_map());
104 static SCOPES: RefCell<Vec<ShortcutMap>> = RefCell::new(Vec::new());
105}
106
107pub fn set(handler: Option<Handler>) {
109 HANDLER.with(|h| *h.borrow_mut() = handler);
110}
111
112pub fn handle(action: Action) -> bool {
114 HANDLER.with(|h| h.borrow().as_ref().map(|f| f(action)).unwrap_or(false))
115}
116
117pub fn resolve_action(chord: KeyChord) -> Option<Action> {
119 if chord.key == Key::Unknown {
120 return None;
121 }
122
123 if let Some(action) = SCOPES.with(|scopes| {
124 scopes
125 .borrow()
126 .iter()
127 .rev()
128 .find_map(|scope| scope.action_for(&chord))
129 }) {
130 return Some(action);
131 }
132
133 DEFAULT_MAP.with(|m| m.borrow().action_for(&chord))
134}
135
136pub fn set_default_map(map: ShortcutMap) {
138 DEFAULT_MAP.with(|m| *m.borrow_mut() = map);
139}
140
141#[allow(non_snake_case)]
143pub fn InstallShortcutMap(map: ShortcutMap) -> Dispose {
144 SCOPES.with(|scopes| scopes.borrow_mut().push(map));
145 on_unmount(|| {
146 SCOPES.with(|scopes| {
147 scopes.borrow_mut().pop();
148 });
149 })
150}
151
152#[allow(non_snake_case)]
154pub fn InstallShortcutHandler(handler: Handler) -> Dispose {
155 set(Some(handler));
156 on_unmount(|| set(None))
157}
158
159pub fn default_chord_for(action: &Action) -> Option<KeyChord> {
160 let cmd = Modifiers {
161 command: true,
162 ..Modifiers::default()
163 };
164 match action {
165 Action::Copy => Some(KeyChord::new(Key::Character('c'), cmd)),
166 Action::Cut => Some(KeyChord::new(Key::Character('x'), cmd)),
167 Action::Paste => Some(KeyChord::new(Key::Character('v'), cmd)),
168 Action::SelectAll => Some(KeyChord::new(Key::Character('a'), cmd)),
169 Action::Undo => Some(KeyChord::new(Key::Character('z'), cmd)),
170 Action::Redo => Some(KeyChord::new(
171 Key::Character('z'),
172 Modifiers {
173 command: true,
174 shift: true,
175 ..Modifiers::default()
176 },
177 )),
178 Action::Find => Some(KeyChord::new(Key::Character('f'), cmd)),
179 Action::Save => Some(KeyChord::new(Key::Character('s'), cmd)),
180 _ => None,
181 }
182}
183
184pub fn default_map() -> ShortcutMap {
185 let mut map = ShortcutMap::new();
186 let actions = [
187 Action::Copy,
188 Action::Cut,
189 Action::Paste,
190 Action::SelectAll,
191 Action::Undo,
192 Action::Redo,
193 Action::Find,
194 Action::Save,
195 ];
196 for action in actions {
197 if let Some(chord) = default_chord_for(&action) {
198 map.insert(chord.key, chord.modifiers, action);
199 }
200 }
201 map
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn resolve_action_prefers_scopes() {
210 let mut map = ShortcutMap::new();
211 map.insert(
212 Key::Character('k'),
213 Modifiers::default(),
214 Action::Custom("one".into()),
215 );
216 set_default_map(map);
217
218 let mut scope = ShortcutMap::new();
219 scope.insert(
220 Key::Character('k'),
221 Modifiers::default(),
222 Action::Custom("two".into()),
223 );
224
225 SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
226
227 let chord = KeyChord::new(Key::Character('k'), Modifiers::default());
228 assert_eq!(
229 resolve_action(chord.clone()),
230 Some(Action::Custom("two".into()))
231 );
232
233 SCOPES.with(|scopes| scopes.borrow_mut().pop());
234 assert_eq!(resolve_action(chord), Some(Action::Custom("one".into())));
235 }
236}