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)]
155pub fn InstallShortcutHandler(handler: Handler) -> Dispose {
156 let prev = HANDLER.with(|h| h.borrow_mut().replace(handler));
157 on_unmount(move || {
158 HANDLER.with(|h| *h.borrow_mut() = prev);
159 })
160}
161
162pub fn default_chord_for(action: &Action) -> Option<KeyChord> {
163 let cmd = Modifiers {
164 command: true,
165 ..Modifiers::default()
166 };
167 match action {
168 Action::Copy => Some(KeyChord::new(Key::Character('c'), cmd)),
169 Action::Cut => Some(KeyChord::new(Key::Character('x'), cmd)),
170 Action::Paste => Some(KeyChord::new(Key::Character('v'), cmd)),
171 Action::SelectAll => Some(KeyChord::new(Key::Character('a'), cmd)),
172 Action::Undo => Some(KeyChord::new(Key::Character('z'), cmd)),
173 Action::Redo => Some(KeyChord::new(
174 Key::Character('z'),
175 Modifiers {
176 command: true,
177 shift: true,
178 ..Modifiers::default()
179 },
180 )),
181 Action::Find => Some(KeyChord::new(Key::Character('f'), cmd)),
182 Action::Save => Some(KeyChord::new(Key::Character('s'), cmd)),
183 _ => None,
184 }
185}
186
187pub fn default_map() -> ShortcutMap {
188 let mut map = ShortcutMap::new();
189 let actions = [
190 Action::Copy,
191 Action::Cut,
192 Action::Paste,
193 Action::SelectAll,
194 Action::Undo,
195 Action::Redo,
196 Action::Find,
197 Action::Save,
198 ];
199 for action in actions {
200 if let Some(chord) = default_chord_for(&action) {
201 map.insert(chord.key, chord.modifiers, action);
202 }
203 }
204 map
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn resolve_action_prefers_scopes() {
213 let mut map = ShortcutMap::new();
214 map.insert(
215 Key::Character('k'),
216 Modifiers::default(),
217 Action::Custom("one".into()),
218 );
219 set_default_map(map);
220
221 let mut scope = ShortcutMap::new();
222 scope.insert(
223 Key::Character('k'),
224 Modifiers::default(),
225 Action::Custom("two".into()),
226 );
227
228 SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
229
230 let chord = KeyChord::new(Key::Character('k'), Modifiers::default());
231 assert_eq!(
232 resolve_action(chord.clone()),
233 Some(Action::Custom("two".into()))
234 );
235
236 SCOPES.with(|scopes| scopes.borrow_mut().pop());
237 assert_eq!(resolve_action(chord), Some(Action::Custom("one".into())));
238 }
239}