1use crate::Vec2;
2use crate::effects::{Dispose, effect_once, 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 {
15 delta_scale: f32,
16 },
17 PinchWithCenter {
18 delta_scale: f32,
19 center: Vec2,
20 },
21 Rotate {
25 delta_rotation: f32,
26 center: Vec2,
27 },
28 Pan {
32 delta: Vec2,
33 center: Vec2,
34 },
35}
36
37#[derive(Clone, Debug, PartialEq)]
41pub enum DragAction {
42 Press {
44 position: Vec2,
45 capture_id: u64,
46 kind: PointerKind,
47 modifiers: Modifiers,
48 },
49 Move {
51 position: Vec2,
52 modifiers: Modifiers,
53 },
54 Release {
56 position: Vec2,
57 modifiers: Modifiers,
58 },
59 Cancel,
61}
62
63#[derive(Clone, Debug, PartialEq)]
64pub enum Action {
65 Copy,
66 Cut,
67 Paste,
68 SelectAll,
69 Undo,
70 Redo,
71
72 Back,
73 Find,
74 Save,
75
76 FocusNext,
77 FocusPrevious,
78 FocusLeft,
79 FocusRight,
80 FocusUp,
81 FocusDown,
82
83 Gesture(Gesture),
84 Drag(DragAction),
85 Custom(Rc<str>),
86}
87
88#[derive(Clone, Debug, PartialEq, Eq, Hash)]
89pub struct KeyChord {
90 pub key: Key,
91 pub modifiers: Modifiers,
92}
93
94impl KeyChord {
95 pub fn new(key: Key, modifiers: Modifiers) -> Self {
96 Self { key, modifiers }
97 }
98}
99
100#[derive(Clone, Debug)]
101pub struct ShortcutBinding {
102 pub chord: KeyChord,
103 pub action: Action,
104}
105
106#[derive(Clone, Debug, Default)]
107pub struct ShortcutMap {
108 pub bindings: Vec<ShortcutBinding>,
109}
110
111impl ShortcutMap {
112 pub fn new() -> Self {
113 Self {
114 bindings: Vec::new(),
115 }
116 }
117
118 pub fn bind(mut self, key: Key, modifiers: Modifiers, action: Action) -> Self {
119 self.bindings.push(ShortcutBinding {
120 chord: KeyChord::new(key, modifiers),
121 action,
122 });
123 self
124 }
125
126 pub fn bind_action(mut self, action: Action) -> Self {
127 if let Some(chord) = default_chord_for(&action) {
128 self.bindings.push(ShortcutBinding { chord, action });
129 }
130 self
131 }
132
133 pub fn merge(mut self, other: ShortcutMap) -> Self {
134 self.bindings.extend(other.bindings);
135 self
136 }
137
138 pub fn insert(&mut self, key: Key, modifiers: Modifiers, action: Action) {
139 self.bindings.push(ShortcutBinding {
140 chord: KeyChord::new(key, modifiers),
141 action,
142 });
143 }
144
145 pub fn action_for(&self, chord: &KeyChord) -> Option<Action> {
146 self.bindings
147 .iter()
148 .rev()
149 .find(|binding| &binding.chord == chord)
150 .map(|binding| binding.action.clone())
151 }
152}
153
154pub type Handler = Rc<dyn Fn(Action) -> bool>;
155
156#[derive(Clone)]
157pub struct ShortcutState {
158 pub handler: Option<Handler>,
159 pub default_map: ShortcutMap,
160 pub scopes: Vec<ShortcutMap>,
161}
162
163impl ShortcutState {
164 pub fn new() -> Self {
165 Self {
166 handler: None,
167 default_map: default_map(),
168 scopes: Vec::new(),
169 }
170 }
171
172 pub fn resolve_action(&self, chord: &KeyChord) -> Option<Action> {
173 if chord.key == Key::Unknown {
174 return None;
175 }
176 if let Some(action) = self
177 .scopes
178 .iter()
179 .rev()
180 .find_map(|scope| scope.action_for(chord))
181 {
182 return Some(action);
183 }
184 if let Some(action) = self.default_map.action_for(chord) {
185 return Some(action);
186 }
187 resolve_action(chord.clone())
188 }
189
190 pub fn handle(&self, action: Action) -> bool {
191 if self
192 .handler
193 .as_ref()
194 .map(|f| f(action.clone()))
195 .unwrap_or(false)
196 {
197 return true;
198 }
199 handle(action)
200 }
201}
202
203impl Default for ShortcutState {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209thread_local! {
210 static HANDLER: RefCell<Option<Handler>> = RefCell::new(None);
211 static DEFAULT_MAP: RefCell<ShortcutMap> = RefCell::new(default_map());
212 static SCOPES: RefCell<Vec<ShortcutMap>> = const { RefCell::new(Vec::new()) };
213}
214
215pub fn set(handler: Option<Handler>) {
217 HANDLER.with(|h| *h.borrow_mut() = handler);
218}
219
220pub fn handle(action: Action) -> bool {
222 HANDLER.with(|h| h.borrow().as_ref().map(|f| f(action)).unwrap_or(false))
223}
224
225pub fn resolve_action(chord: KeyChord) -> Option<Action> {
227 if chord.key == Key::Unknown {
228 return None;
229 }
230
231 if let Some(action) = SCOPES.with(|scopes| {
232 scopes
233 .borrow()
234 .iter()
235 .rev()
236 .find_map(|scope| scope.action_for(&chord))
237 }) {
238 return Some(action);
239 }
240
241 DEFAULT_MAP.with(|m| m.borrow().action_for(&chord))
242}
243
244pub fn set_default_map(map: ShortcutMap) {
246 DEFAULT_MAP.with(|m| *m.borrow_mut() = map);
247}
248
249#[allow(non_snake_case)]
254pub fn InstallShortcutMap(map: ShortcutMap) -> Dispose {
255 effect_once(move || {
256 let map = map.clone();
257 SCOPES.with(|scopes| scopes.borrow_mut().push(map));
258 on_unmount(|| {
259 let _ = SCOPES.try_with(|scopes| {
260 scopes.borrow_mut().pop();
261 });
262 })
263 })
264}
265
266#[allow(non_snake_case)]
271pub fn InstallShortcutHandler(handler: Handler) -> Dispose {
272 effect_once(move || {
273 let prev = HANDLER.with(|h| h.borrow_mut().replace(handler.clone()));
274 on_unmount(move || {
275 let _ = HANDLER.try_with(|h| *h.borrow_mut() = prev);
276 })
277 })
278}
279
280pub fn default_chord_for(action: &Action) -> Option<KeyChord> {
281 let cmd = Modifiers {
283 command: true,
284 ctrl: !cfg!(target_os = "macos"),
285 ..Modifiers::default()
286 };
287 match action {
288 Action::Copy => Some(KeyChord::new(Key::Character('c'), cmd)),
289 Action::Cut => Some(KeyChord::new(Key::Character('x'), cmd)),
290 Action::Paste => Some(KeyChord::new(Key::Character('v'), cmd)),
291 Action::SelectAll => Some(KeyChord::new(Key::Character('a'), cmd)),
292 Action::Undo => Some(KeyChord::new(Key::Character('z'), cmd)),
293 Action::Redo => Some(KeyChord::new(
294 Key::Character('z'),
295 Modifiers {
296 command: true,
297 shift: true,
298 ctrl: !cfg!(target_os = "macos"),
299 ..Modifiers::default()
300 },
301 )),
302 Action::Find => Some(KeyChord::new(Key::Character('f'), cmd)),
303 Action::Save => Some(KeyChord::new(Key::Character('s'), cmd)),
304 Action::FocusNext => Some(KeyChord::new(Key::Tab, Modifiers::default())),
305 Action::FocusPrevious => Some(KeyChord::new(
306 Key::Tab,
307 Modifiers {
308 shift: true,
309 ..Modifiers::default()
310 },
311 )),
312 Action::FocusLeft => Some(KeyChord::new(Key::ArrowLeft, Modifiers::default())),
313 Action::FocusRight => Some(KeyChord::new(Key::ArrowRight, Modifiers::default())),
314 Action::FocusUp => Some(KeyChord::new(Key::ArrowUp, Modifiers::default())),
315 Action::FocusDown => Some(KeyChord::new(Key::ArrowDown, Modifiers::default())),
316 _ => None,
317 }
318}
319
320pub fn default_map() -> ShortcutMap {
321 let mut map = ShortcutMap::new();
322 let actions = vec![
323 Action::Copy,
324 Action::Cut,
325 Action::Paste,
326 Action::SelectAll,
327 Action::Undo,
328 Action::Redo,
329 Action::Find,
330 Action::Save,
331 Action::FocusNext,
332 Action::FocusPrevious,
333 Action::FocusLeft,
334 Action::FocusRight,
335 Action::FocusUp,
336 Action::FocusDown,
337 ];
338 for action in actions {
339 if let Some(chord) = default_chord_for(&action) {
340 map.insert(chord.key, chord.modifiers, action);
341 }
342 }
343 map
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349
350 #[test]
351 fn resolve_action_prefers_scopes() {
352 let mut map = ShortcutMap::new();
353 map.insert(
354 Key::Character('k'),
355 Modifiers::default(),
356 Action::Custom("one".into()),
357 );
358 set_default_map(map);
359
360 let mut scope = ShortcutMap::new();
361 scope.insert(
362 Key::Character('k'),
363 Modifiers::default(),
364 Action::Custom("two".into()),
365 );
366
367 SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
368
369 let chord = KeyChord::new(Key::Character('k'), Modifiers::default());
370 assert_eq!(
371 resolve_action(chord.clone()),
372 Some(Action::Custom("two".into()))
373 );
374
375 SCOPES.with(|scopes| scopes.borrow_mut().pop());
376 assert_eq!(resolve_action(chord), Some(Action::Custom("one".into())));
377 }
378}