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