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 {
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
156thread_local! {
157 static HANDLER: RefCell<Option<Handler>> = RefCell::new(None);
158 static DEFAULT_MAP: RefCell<ShortcutMap> = RefCell::new(default_map());
159 static SCOPES: RefCell<Vec<ShortcutMap>> = const { RefCell::new(Vec::new()) };
160}
161
162pub fn set(handler: Option<Handler>) {
164 HANDLER.with(|h| *h.borrow_mut() = handler);
165}
166
167pub fn handle(action: Action) -> bool {
169 HANDLER.with(|h| h.borrow().as_ref().map(|f| f(action)).unwrap_or(false))
170}
171
172pub fn resolve_action(chord: KeyChord) -> Option<Action> {
174 if chord.key == Key::Unknown {
175 return None;
176 }
177
178 if let Some(action) = SCOPES.with(|scopes| {
179 scopes
180 .borrow()
181 .iter()
182 .rev()
183 .find_map(|scope| scope.action_for(&chord))
184 }) {
185 return Some(action);
186 }
187
188 DEFAULT_MAP.with(|m| m.borrow().action_for(&chord))
189}
190
191pub fn set_default_map(map: ShortcutMap) {
193 DEFAULT_MAP.with(|m| *m.borrow_mut() = map);
194}
195
196#[allow(non_snake_case)]
198pub fn InstallShortcutMap(map: ShortcutMap) -> Dispose {
199 SCOPES.with(|scopes| scopes.borrow_mut().push(map));
200 on_unmount(|| {
201 let _ = SCOPES.try_with(|scopes| {
202 scopes.borrow_mut().pop();
203 });
204 })
205}
206
207#[allow(non_snake_case)]
210pub fn InstallShortcutHandler(handler: Handler) -> Dispose {
211 let prev = HANDLER.with(|h| h.borrow_mut().replace(handler));
212 on_unmount(move || {
213 let _ = HANDLER.try_with(|h| *h.borrow_mut() = prev);
214 })
215}
216
217pub fn default_chord_for(action: &Action) -> Option<KeyChord> {
218 let cmd = Modifiers {
220 command: true,
221 ctrl: !cfg!(target_os = "macos"),
222 ..Modifiers::default()
223 };
224 match action {
225 Action::Copy => Some(KeyChord::new(Key::Character('c'), cmd)),
226 Action::Cut => Some(KeyChord::new(Key::Character('x'), cmd)),
227 Action::Paste => Some(KeyChord::new(Key::Character('v'), cmd)),
228 Action::SelectAll => Some(KeyChord::new(Key::Character('a'), cmd)),
229 Action::Undo => Some(KeyChord::new(Key::Character('z'), cmd)),
230 Action::Redo => Some(KeyChord::new(
231 Key::Character('z'),
232 Modifiers {
233 command: true,
234 shift: true,
235 ctrl: !cfg!(target_os = "macos"),
236 ..Modifiers::default()
237 },
238 )),
239 Action::Find => Some(KeyChord::new(Key::Character('f'), cmd)),
240 Action::Save => Some(KeyChord::new(Key::Character('s'), cmd)),
241 Action::FocusNext => Some(KeyChord::new(Key::Tab, Modifiers::default())),
242 Action::FocusPrevious => Some(KeyChord::new(
243 Key::Tab,
244 Modifiers {
245 shift: true,
246 ..Modifiers::default()
247 },
248 )),
249 Action::FocusLeft => Some(KeyChord::new(Key::ArrowLeft, Modifiers::default())),
250 Action::FocusRight => Some(KeyChord::new(Key::ArrowRight, Modifiers::default())),
251 Action::FocusUp => Some(KeyChord::new(Key::ArrowUp, Modifiers::default())),
252 Action::FocusDown => Some(KeyChord::new(Key::ArrowDown, Modifiers::default())),
253 _ => None,
254 }
255}
256
257pub fn default_map() -> ShortcutMap {
258 let mut map = ShortcutMap::new();
259 let actions = vec![
260 Action::Copy,
261 Action::Cut,
262 Action::Paste,
263 Action::SelectAll,
264 Action::Undo,
265 Action::Redo,
266 Action::Find,
267 Action::Save,
268 Action::FocusNext,
269 Action::FocusPrevious,
270 Action::FocusLeft,
271 Action::FocusRight,
272 Action::FocusUp,
273 Action::FocusDown,
274 ];
275 for action in actions {
276 if let Some(chord) = default_chord_for(&action) {
277 map.insert(chord.key, chord.modifiers, action);
278 }
279 }
280 map
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn resolve_action_prefers_scopes() {
289 let mut map = ShortcutMap::new();
290 map.insert(
291 Key::Character('k'),
292 Modifiers::default(),
293 Action::Custom("one".into()),
294 );
295 set_default_map(map);
296
297 let mut scope = ShortcutMap::new();
298 scope.insert(
299 Key::Character('k'),
300 Modifiers::default(),
301 Action::Custom("two".into()),
302 );
303
304 SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
305
306 let chord = KeyChord::new(Key::Character('k'), Modifiers::default());
307 assert_eq!(
308 resolve_action(chord.clone()),
309 Some(Action::Custom("two".into()))
310 );
311
312 SCOPES.with(|scopes| scopes.borrow_mut().pop());
313 assert_eq!(resolve_action(chord), Some(Action::Custom("one".into())));
314 }
315}