1use crate::Vec2;
2use crate::effects::{Dispose, on_unmount};
3use crate::input::{Key, Modifiers, PointerKind};
4use std::cell::RefCell;
5use std::rc::Rc;
6
7#[derive(Clone, Debug, PartialEq)]
8pub enum Gesture {
9 SwipeLeft,
10 SwipeRight,
11 Pinch {
12 delta_scale: f32,
13 },
14 PinchWithCenter {
15 delta_scale: f32,
16 center: Vec2,
17 },
18 Pan {
21 delta: Vec2,
22 },
23}
24
25#[derive(Clone, Debug, PartialEq)]
29pub enum DragAction {
30 Press {
32 position: Vec2,
33 capture_id: u64,
34 kind: PointerKind,
35 modifiers: Modifiers,
36 },
37 Move {
39 position: Vec2,
40 modifiers: Modifiers,
41 },
42 Release {
44 position: Vec2,
45 modifiers: Modifiers,
46 },
47 Cancel,
49}
50
51#[derive(Clone, Debug, PartialEq)]
52pub enum Action {
53 Copy,
54 Cut,
55 Paste,
56 SelectAll,
57 Undo,
58 Redo,
59
60 Back,
61 Find,
62 Save,
63
64 FocusNext,
65 FocusPrevious,
66 FocusLeft,
67 FocusRight,
68 FocusUp,
69 FocusDown,
70
71 Gesture(Gesture),
72 Drag(DragAction),
73 Custom(Rc<str>),
74}
75
76#[derive(Clone, Debug, PartialEq, Eq, Hash)]
77pub struct KeyChord {
78 pub key: Key,
79 pub modifiers: Modifiers,
80}
81
82impl KeyChord {
83 pub fn new(key: Key, modifiers: Modifiers) -> Self {
84 Self { key, modifiers }
85 }
86}
87
88#[derive(Clone, Debug)]
89pub struct ShortcutBinding {
90 pub chord: KeyChord,
91 pub action: Action,
92}
93
94#[derive(Clone, Debug, Default)]
95pub struct ShortcutMap {
96 pub bindings: Vec<ShortcutBinding>,
97}
98
99impl ShortcutMap {
100 pub fn new() -> Self {
101 Self {
102 bindings: Vec::new(),
103 }
104 }
105
106 pub fn bind(mut self, key: Key, modifiers: Modifiers, action: Action) -> Self {
107 self.bindings.push(ShortcutBinding {
108 chord: KeyChord::new(key, modifiers),
109 action,
110 });
111 self
112 }
113
114 pub fn bind_action(mut self, action: Action) -> Self {
115 if let Some(chord) = default_chord_for(&action) {
116 self.bindings.push(ShortcutBinding { chord, action });
117 }
118 self
119 }
120
121 pub fn merge(mut self, other: ShortcutMap) -> Self {
122 self.bindings.extend(other.bindings);
123 self
124 }
125
126 pub fn insert(&mut self, key: Key, modifiers: Modifiers, action: Action) {
127 self.bindings.push(ShortcutBinding {
128 chord: KeyChord::new(key, modifiers),
129 action,
130 });
131 }
132
133 pub fn action_for(&self, chord: &KeyChord) -> Option<Action> {
134 self.bindings
135 .iter()
136 .rev()
137 .find(|binding| &binding.chord == chord)
138 .map(|binding| binding.action.clone())
139 }
140}
141
142pub type Handler = Rc<dyn Fn(Action) -> bool>;
143
144thread_local! {
145 static HANDLER: RefCell<Option<Handler>> = RefCell::new(None);
146 static DEFAULT_MAP: RefCell<ShortcutMap> = RefCell::new(default_map());
147 static SCOPES: RefCell<Vec<ShortcutMap>> = const { RefCell::new(Vec::new()) };
148}
149
150pub fn set(handler: Option<Handler>) {
152 HANDLER.with(|h| *h.borrow_mut() = handler);
153}
154
155pub fn handle(action: Action) -> bool {
157 HANDLER.with(|h| h.borrow().as_ref().map(|f| f(action)).unwrap_or(false))
158}
159
160pub fn resolve_action(chord: KeyChord) -> Option<Action> {
162 if chord.key == Key::Unknown {
163 return None;
164 }
165
166 if let Some(action) = SCOPES.with(|scopes| {
167 scopes
168 .borrow()
169 .iter()
170 .rev()
171 .find_map(|scope| scope.action_for(&chord))
172 }) {
173 return Some(action);
174 }
175
176 DEFAULT_MAP.with(|m| m.borrow().action_for(&chord))
177}
178
179pub fn set_default_map(map: ShortcutMap) {
181 DEFAULT_MAP.with(|m| *m.borrow_mut() = map);
182}
183
184#[allow(non_snake_case)]
186pub fn InstallShortcutMap(map: ShortcutMap) -> Dispose {
187 SCOPES.with(|scopes| scopes.borrow_mut().push(map));
188 on_unmount(|| {
189 let _ = SCOPES.try_with(|scopes| {
190 scopes.borrow_mut().pop();
191 });
192 })
193}
194
195#[allow(non_snake_case)]
198pub fn InstallShortcutHandler(handler: Handler) -> Dispose {
199 let prev = HANDLER.with(|h| h.borrow_mut().replace(handler));
200 on_unmount(move || {
201 let _ = HANDLER.try_with(|h| *h.borrow_mut() = prev);
202 })
203}
204
205pub fn default_chord_for(action: &Action) -> Option<KeyChord> {
206 let cmd = Modifiers {
208 command: true,
209 ctrl: !cfg!(target_os = "macos"),
210 ..Modifiers::default()
211 };
212 match action {
213 Action::Copy => Some(KeyChord::new(Key::Character('c'), cmd)),
214 Action::Cut => Some(KeyChord::new(Key::Character('x'), cmd)),
215 Action::Paste => Some(KeyChord::new(Key::Character('v'), cmd)),
216 Action::SelectAll => Some(KeyChord::new(Key::Character('a'), cmd)),
217 Action::Undo => Some(KeyChord::new(Key::Character('z'), cmd)),
218 Action::Redo => Some(KeyChord::new(
219 Key::Character('z'),
220 Modifiers {
221 command: true,
222 shift: true,
223 ctrl: !cfg!(target_os = "macos"),
224 ..Modifiers::default()
225 },
226 )),
227 Action::Find => Some(KeyChord::new(Key::Character('f'), cmd)),
228 Action::Save => Some(KeyChord::new(Key::Character('s'), cmd)),
229 Action::FocusNext => Some(KeyChord::new(Key::Tab, Modifiers::default())),
230 Action::FocusPrevious => Some(KeyChord::new(
231 Key::Tab,
232 Modifiers {
233 shift: true,
234 ..Modifiers::default()
235 },
236 )),
237 Action::FocusLeft => Some(KeyChord::new(Key::ArrowLeft, Modifiers::default())),
238 Action::FocusRight => Some(KeyChord::new(Key::ArrowRight, Modifiers::default())),
239 Action::FocusUp => Some(KeyChord::new(Key::ArrowUp, Modifiers::default())),
240 Action::FocusDown => Some(KeyChord::new(Key::ArrowDown, Modifiers::default())),
241 _ => None,
242 }
243}
244
245pub fn default_map() -> ShortcutMap {
246 let mut map = ShortcutMap::new();
247 let actions = vec![
248 Action::Copy,
249 Action::Cut,
250 Action::Paste,
251 Action::SelectAll,
252 Action::Undo,
253 Action::Redo,
254 Action::Find,
255 Action::Save,
256 Action::FocusNext,
257 Action::FocusPrevious,
258 Action::FocusLeft,
259 Action::FocusRight,
260 Action::FocusUp,
261 Action::FocusDown,
262 ];
263 for action in actions {
264 if let Some(chord) = default_chord_for(&action) {
265 map.insert(chord.key, chord.modifiers, action);
266 }
267 }
268 map
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn resolve_action_prefers_scopes() {
277 let mut map = ShortcutMap::new();
278 map.insert(
279 Key::Character('k'),
280 Modifiers::default(),
281 Action::Custom("one".into()),
282 );
283 set_default_map(map);
284
285 let mut scope = ShortcutMap::new();
286 scope.insert(
287 Key::Character('k'),
288 Modifiers::default(),
289 Action::Custom("two".into()),
290 );
291
292 SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
293
294 let chord = KeyChord::new(Key::Character('k'), Modifiers::default());
295 assert_eq!(
296 resolve_action(chord.clone()),
297 Some(Action::Custom("two".into()))
298 );
299
300 SCOPES.with(|scopes| scopes.borrow_mut().pop());
301 assert_eq!(resolve_action(chord), Some(Action::Custom("one".into())));
302 }
303}