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