use crate::effects::{Dispose, on_unmount};
use crate::input::{Key, Modifiers};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Debug, PartialEq)]
pub enum Gesture {
SwipeLeft,
SwipeRight,
Pinch {
delta_scale: f32,
},
}
#[derive(Clone, Debug, PartialEq)]
pub enum Action {
Copy,
Cut,
Paste,
SelectAll,
Undo,
Redo,
Back,
Find,
Save,
Gesture(Gesture),
Custom(Rc<str>),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct KeyChord {
pub key: Key,
pub modifiers: Modifiers,
}
impl KeyChord {
pub fn new(key: Key, modifiers: Modifiers) -> Self {
Self { key, modifiers }
}
}
#[derive(Clone, Debug)]
pub struct ShortcutBinding {
pub chord: KeyChord,
pub action: Action,
}
#[derive(Clone, Debug, Default)]
pub struct ShortcutMap {
pub bindings: Vec<ShortcutBinding>,
}
impl ShortcutMap {
pub fn new() -> Self {
Self {
bindings: Vec::new(),
}
}
pub fn bind(mut self, key: Key, modifiers: Modifiers, action: Action) -> Self {
self.bindings.push(ShortcutBinding {
chord: KeyChord::new(key, modifiers),
action,
});
self
}
pub fn bind_action(mut self, action: Action) -> Self {
if let Some(chord) = default_chord_for(&action) {
self.bindings.push(ShortcutBinding { chord, action });
}
self
}
pub fn merge(mut self, other: ShortcutMap) -> Self {
self.bindings.extend(other.bindings);
self
}
pub fn insert(&mut self, key: Key, modifiers: Modifiers, action: Action) {
self.bindings.push(ShortcutBinding {
chord: KeyChord::new(key, modifiers),
action,
});
}
pub fn action_for(&self, chord: &KeyChord) -> Option<Action> {
self.bindings
.iter()
.rev()
.find(|binding| &binding.chord == chord)
.map(|binding| binding.action.clone())
}
}
pub type Handler = Rc<dyn Fn(Action) -> bool>;
thread_local! {
static HANDLER: RefCell<Option<Handler>> = RefCell::new(None);
static DEFAULT_MAP: RefCell<ShortcutMap> = RefCell::new(default_map());
static SCOPES: RefCell<Vec<ShortcutMap>> = RefCell::new(Vec::new());
}
pub fn set(handler: Option<Handler>) {
HANDLER.with(|h| *h.borrow_mut() = handler);
}
pub fn handle(action: Action) -> bool {
HANDLER.with(|h| h.borrow().as_ref().map(|f| f(action)).unwrap_or(false))
}
pub fn resolve_action(chord: KeyChord) -> Option<Action> {
if chord.key == Key::Unknown {
return None;
}
if let Some(action) = SCOPES.with(|scopes| {
scopes
.borrow()
.iter()
.rev()
.find_map(|scope| scope.action_for(&chord))
}) {
return Some(action);
}
DEFAULT_MAP.with(|m| m.borrow().action_for(&chord))
}
pub fn set_default_map(map: ShortcutMap) {
DEFAULT_MAP.with(|m| *m.borrow_mut() = map);
}
#[allow(non_snake_case)]
pub fn InstallShortcutMap(map: ShortcutMap) -> Dispose {
SCOPES.with(|scopes| scopes.borrow_mut().push(map));
on_unmount(|| {
SCOPES.with(|scopes| {
scopes.borrow_mut().pop();
});
})
}
#[allow(non_snake_case)]
pub fn InstallShortcutHandler(handler: Handler) -> Dispose {
let prev = HANDLER.with(|h| h.borrow_mut().replace(handler));
on_unmount(move || {
HANDLER.with(|h| *h.borrow_mut() = prev);
})
}
pub fn default_chord_for(action: &Action) -> Option<KeyChord> {
let cmd = Modifiers {
command: true,
..Modifiers::default()
};
match action {
Action::Copy => Some(KeyChord::new(Key::Character('c'), cmd)),
Action::Cut => Some(KeyChord::new(Key::Character('x'), cmd)),
Action::Paste => Some(KeyChord::new(Key::Character('v'), cmd)),
Action::SelectAll => Some(KeyChord::new(Key::Character('a'), cmd)),
Action::Undo => Some(KeyChord::new(Key::Character('z'), cmd)),
Action::Redo => Some(KeyChord::new(
Key::Character('z'),
Modifiers {
command: true,
shift: true,
..Modifiers::default()
},
)),
Action::Find => Some(KeyChord::new(Key::Character('f'), cmd)),
Action::Save => Some(KeyChord::new(Key::Character('s'), cmd)),
_ => None,
}
}
pub fn default_map() -> ShortcutMap {
let mut map = ShortcutMap::new();
let actions = [
Action::Copy,
Action::Cut,
Action::Paste,
Action::SelectAll,
Action::Undo,
Action::Redo,
Action::Find,
Action::Save,
];
for action in actions {
if let Some(chord) = default_chord_for(&action) {
map.insert(chord.key, chord.modifiers, action);
}
}
map
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_action_prefers_scopes() {
let mut map = ShortcutMap::new();
map.insert(
Key::Character('k'),
Modifiers::default(),
Action::Custom("one".into()),
);
set_default_map(map);
let mut scope = ShortcutMap::new();
scope.insert(
Key::Character('k'),
Modifiers::default(),
Action::Custom("two".into()),
);
SCOPES.with(|scopes| scopes.borrow_mut().push(scope));
let chord = KeyChord::new(Key::Character('k'), Modifiers::default());
assert_eq!(
resolve_action(chord.clone()),
Some(Action::Custom("two".into()))
);
SCOPES.with(|scopes| scopes.borrow_mut().pop());
assert_eq!(resolve_action(chord), Some(Action::Custom("one".into())));
}
}