use crate::hooks::use_input::{Key, use_input};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
impl Modifiers {
pub fn none() -> Self {
Self::default()
}
pub fn ctrl() -> Self {
Self {
ctrl: true,
..Default::default()
}
}
pub fn alt() -> Self {
Self {
alt: true,
..Default::default()
}
}
pub fn shift() -> Self {
Self {
shift: true,
..Default::default()
}
}
pub fn ctrl_shift() -> Self {
Self {
ctrl: true,
shift: true,
..Default::default()
}
}
pub fn ctrl_alt() -> Self {
Self {
ctrl: true,
alt: true,
..Default::default()
}
}
pub fn alt_shift() -> Self {
Self {
alt: true,
shift: true,
..Default::default()
}
}
pub fn all() -> Self {
Self {
ctrl: true,
alt: true,
shift: true,
}
}
pub fn matches(&self, key: &Key) -> bool {
self.ctrl == key.ctrl && self.alt == key.alt && self.shift == key.shift
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shortcut {
pub key: ShortcutKey,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ShortcutKey {
Char(char),
F(u8),
Enter,
Escape,
Tab,
Backspace,
Delete,
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
Insert,
Space,
}
impl Shortcut {
pub fn new(key: ShortcutKey, modifiers: Modifiers) -> Self {
Self { key, modifiers }
}
pub fn char(c: char) -> Self {
Self::new(ShortcutKey::Char(c), Modifiers::none())
}
pub fn ctrl(c: char) -> Self {
Self::new(ShortcutKey::Char(c), Modifiers::ctrl())
}
pub fn alt(c: char) -> Self {
Self::new(ShortcutKey::Char(c), Modifiers::alt())
}
pub fn shift(c: char) -> Self {
Self::new(ShortcutKey::Char(c), Modifiers::shift())
}
pub fn ctrl_shift(c: char) -> Self {
Self::new(ShortcutKey::Char(c), Modifiers::ctrl_shift())
}
pub fn ctrl_alt(c: char) -> Self {
Self::new(ShortcutKey::Char(c), Modifiers::ctrl_alt())
}
pub fn f(n: u8) -> Self {
Self::new(ShortcutKey::F(n), Modifiers::none())
}
pub fn ctrl_f(n: u8) -> Self {
Self::new(ShortcutKey::F(n), Modifiers::ctrl())
}
pub fn enter() -> Self {
Self::new(ShortcutKey::Enter, Modifiers::none())
}
pub fn escape() -> Self {
Self::new(ShortcutKey::Escape, Modifiers::none())
}
pub fn tab() -> Self {
Self::new(ShortcutKey::Tab, Modifiers::none())
}
pub fn shift_tab() -> Self {
Self::new(ShortcutKey::Tab, Modifiers::shift())
}
pub fn space() -> Self {
Self::new(ShortcutKey::Space, Modifiers::none())
}
pub fn up() -> Self {
Self::new(ShortcutKey::Up, Modifiers::none())
}
pub fn down() -> Self {
Self::new(ShortcutKey::Down, Modifiers::none())
}
pub fn left() -> Self {
Self::new(ShortcutKey::Left, Modifiers::none())
}
pub fn right() -> Self {
Self::new(ShortcutKey::Right, Modifiers::none())
}
pub fn matches(&self, input: &str, key: &Key) -> bool {
if !self.modifiers.matches(key) {
return false;
}
match &self.key {
ShortcutKey::Char(c) => {
let mut chars = input.chars();
match (chars.next(), chars.next()) {
(Some(input_char), None) => input_char.eq_ignore_ascii_case(c),
_ => false,
}
}
ShortcutKey::F(1) => key.f1,
ShortcutKey::F(2) => key.f2,
ShortcutKey::F(3) => key.f3,
ShortcutKey::F(4) => key.f4,
ShortcutKey::F(5) => key.f5,
ShortcutKey::F(6) => key.f6,
ShortcutKey::F(7) => key.f7,
ShortcutKey::F(8) => key.f8,
ShortcutKey::F(9) => key.f9,
ShortcutKey::F(10) => key.f10,
ShortcutKey::F(11) => key.f11,
ShortcutKey::F(12) => key.f12,
ShortcutKey::F(_) => false,
ShortcutKey::Enter => key.return_key,
ShortcutKey::Escape => key.escape,
ShortcutKey::Tab => key.tab,
ShortcutKey::Backspace => key.backspace,
ShortcutKey::Delete => key.delete,
ShortcutKey::Up => key.up_arrow,
ShortcutKey::Down => key.down_arrow,
ShortcutKey::Left => key.left_arrow,
ShortcutKey::Right => key.right_arrow,
ShortcutKey::Home => key.home,
ShortcutKey::End => key.end,
ShortcutKey::PageUp => key.page_up,
ShortcutKey::PageDown => key.page_down,
ShortcutKey::Insert => key.insert,
ShortcutKey::Space => key.space,
}
}
pub fn description(&self) -> String {
let mut parts = Vec::new();
if self.modifiers.ctrl {
parts.push("Ctrl".to_string());
}
if self.modifiers.alt {
parts.push("Alt".to_string());
}
if self.modifiers.shift {
parts.push("Shift".to_string());
}
let key_str = match &self.key {
ShortcutKey::Char(c) => c.to_uppercase().to_string(),
ShortcutKey::F(n) => format!("F{}", n),
ShortcutKey::Enter => "Enter".to_string(),
ShortcutKey::Escape => "Esc".to_string(),
ShortcutKey::Tab => "Tab".to_string(),
ShortcutKey::Backspace => "Backspace".to_string(),
ShortcutKey::Delete => "Delete".to_string(),
ShortcutKey::Up => "↑".to_string(),
ShortcutKey::Down => "↓".to_string(),
ShortcutKey::Left => "←".to_string(),
ShortcutKey::Right => "→".to_string(),
ShortcutKey::Home => "Home".to_string(),
ShortcutKey::End => "End".to_string(),
ShortcutKey::PageUp => "PgUp".to_string(),
ShortcutKey::PageDown => "PgDn".to_string(),
ShortcutKey::Insert => "Insert".to_string(),
ShortcutKey::Space => "Space".to_string(),
};
parts.push(key_str);
parts.join("+")
}
}
pub fn use_keyboard_shortcut<F>(shortcut: Shortcut, callback: F)
where
F: Fn() + 'static,
{
use_input(move |input, key| {
if shortcut.matches(input, key) {
callback();
}
});
}
pub fn use_keyboard_shortcuts<F>(shortcuts: Vec<(Shortcut, F)>)
where
F: Fn() + 'static,
{
use_input(move |input, key| {
for (shortcut, callback) in &shortcuts {
if shortcut.matches(input, key) {
callback();
break;
}
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_modifiers() {
let none = Modifiers::none();
assert!(!none.ctrl && !none.alt && !none.shift);
let ctrl = Modifiers::ctrl();
assert!(ctrl.ctrl && !ctrl.alt && !ctrl.shift);
let ctrl_shift = Modifiers::ctrl_shift();
assert!(ctrl_shift.ctrl && !ctrl_shift.alt && ctrl_shift.shift);
}
#[test]
fn test_shortcut_creation() {
let s = Shortcut::ctrl('s');
assert_eq!(s.key, ShortcutKey::Char('s'));
assert!(s.modifiers.ctrl);
let f1 = Shortcut::f(1);
assert_eq!(f1.key, ShortcutKey::F(1));
}
#[test]
fn test_shortcut_matches_ctrl_char() {
let shortcut = Shortcut::ctrl('s');
let mut key = Key::default();
key.ctrl = true;
assert!(shortcut.matches("s", &key));
assert!(shortcut.matches("S", &key));
key.ctrl = false;
assert!(!shortcut.matches("s", &key));
}
#[test]
fn test_shortcut_matches_plain_char() {
let shortcut = Shortcut::char('a');
let key = Key::default();
assert!(shortcut.matches("a", &key));
assert!(shortcut.matches("A", &key));
assert!(!shortcut.matches("b", &key));
assert!(!shortcut.matches("", &key));
assert!(!shortcut.matches("ab", &key));
}
#[test]
fn test_shortcut_matches_function_key() {
let shortcut = Shortcut::f(1);
let mut key = Key::default();
key.f1 = true;
assert!(shortcut.matches("", &key));
key.f1 = false;
key.f2 = true;
assert!(!shortcut.matches("", &key));
}
#[test]
fn test_shortcut_matches_special_keys() {
let mut key = Key::default();
key.return_key = true;
assert!(Shortcut::enter().matches("", &key));
key.return_key = false;
key.escape = true;
assert!(Shortcut::escape().matches("", &key));
key.escape = false;
key.tab = true;
assert!(Shortcut::tab().matches("", &key));
key.tab = false;
key.up_arrow = true;
assert!(Shortcut::up().matches("", &key));
key.up_arrow = false;
key.down_arrow = true;
assert!(Shortcut::down().matches("", &key));
key.down_arrow = false;
key.left_arrow = true;
assert!(Shortcut::left().matches("", &key));
key.left_arrow = false;
key.right_arrow = true;
assert!(Shortcut::right().matches("", &key));
}
#[test]
fn test_shortcut_description() {
assert_eq!(Shortcut::ctrl('s').description(), "Ctrl+S");
assert_eq!(Shortcut::ctrl_shift('z').description(), "Ctrl+Shift+Z");
assert_eq!(Shortcut::f(1).description(), "F1");
assert_eq!(Shortcut::escape().description(), "Esc");
assert_eq!(Shortcut::shift_tab().description(), "Shift+Tab");
}
#[test]
fn test_modifiers_matches() {
let mut key = Key::default();
key.ctrl = true;
key.shift = true;
assert!(Modifiers::ctrl_shift().matches(&key));
assert!(!Modifiers::ctrl().matches(&key));
assert!(!Modifiers::none().matches(&key));
}
#[test]
fn test_use_keyboard_shortcut_compiles() {
fn _test() {
use_keyboard_shortcut(Shortcut::ctrl('s'), || {});
}
}
}