use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
#[derive(Debug, Clone, Copy, Default)]
pub struct Key {
pub up_arrow: bool,
pub down_arrow: bool,
pub left_arrow: bool,
pub right_arrow: bool,
pub page_up: bool,
pub page_down: bool,
pub home: bool,
pub end: bool,
pub insert: bool,
pub return_key: bool,
pub escape: bool,
pub tab: bool,
pub backspace: bool,
pub delete: bool,
pub space: bool,
pub f1: bool,
pub f2: bool,
pub f3: bool,
pub f4: bool,
pub f5: bool,
pub f6: bool,
pub f7: bool,
pub f8: bool,
pub f9: bool,
pub f10: bool,
pub f11: bool,
pub f12: bool,
pub ctrl: bool,
pub shift: bool,
pub alt: bool,
pub meta: bool,
pub media_play: bool,
pub media_pause: bool,
pub media_play_pause: bool,
pub media_stop: bool,
pub media_next: bool,
pub media_previous: bool,
pub volume_up: bool,
pub volume_down: bool,
pub volume_mute: bool,
}
impl Key {
pub fn from_event(event: &KeyEvent) -> Self {
let modifiers = event.modifiers;
Self {
up_arrow: event.code == KeyCode::Up,
down_arrow: event.code == KeyCode::Down,
left_arrow: event.code == KeyCode::Left,
right_arrow: event.code == KeyCode::Right,
page_up: event.code == KeyCode::PageUp,
page_down: event.code == KeyCode::PageDown,
home: event.code == KeyCode::Home,
end: event.code == KeyCode::End,
insert: event.code == KeyCode::Insert,
return_key: event.code == KeyCode::Enter,
escape: event.code == KeyCode::Esc,
tab: event.code == KeyCode::Tab,
backspace: event.code == KeyCode::Backspace,
delete: event.code == KeyCode::Delete,
space: event.code == KeyCode::Char(' '),
f1: event.code == KeyCode::F(1),
f2: event.code == KeyCode::F(2),
f3: event.code == KeyCode::F(3),
f4: event.code == KeyCode::F(4),
f5: event.code == KeyCode::F(5),
f6: event.code == KeyCode::F(6),
f7: event.code == KeyCode::F(7),
f8: event.code == KeyCode::F(8),
f9: event.code == KeyCode::F(9),
f10: event.code == KeyCode::F(10),
f11: event.code == KeyCode::F(11),
f12: event.code == KeyCode::F(12),
ctrl: modifiers.contains(KeyModifiers::CONTROL),
shift: modifiers.contains(KeyModifiers::SHIFT),
alt: modifiers.contains(KeyModifiers::ALT),
meta: modifiers.contains(KeyModifiers::SUPER),
media_play: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::Play),
media_pause: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::Pause),
media_play_pause: event.code
== KeyCode::Media(crossterm::event::MediaKeyCode::PlayPause),
media_stop: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::Stop),
media_next: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::TrackNext),
media_previous: event.code
== KeyCode::Media(crossterm::event::MediaKeyCode::TrackPrevious),
volume_up: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::RaiseVolume),
volume_down: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::LowerVolume),
volume_mute: event.code == KeyCode::Media(crossterm::event::MediaKeyCode::MuteVolume),
}
}
pub fn char_from_event(event: &KeyEvent) -> String {
match event.code {
KeyCode::Char(c) => {
if event.modifiers.contains(KeyModifiers::CONTROL) {
c.to_string()
} else {
c.to_string()
}
}
KeyCode::Enter => String::new(),
KeyCode::Tab => String::new(),
KeyCode::Backspace => String::new(),
KeyCode::Delete => String::new(),
KeyCode::Esc => String::new(),
_ => String::new(),
}
}
}
pub type InputHandler = Box<dyn Fn(&str, &Key)>;
use std::cell::RefCell;
use std::rc::Rc;
type InputHandlerRc = Rc<dyn Fn(&str, &Key)>;
thread_local! {
static INPUT_HANDLERS: RefCell<Vec<InputHandlerRc>> = RefCell::new(Vec::new());
}
pub fn register_input_handler<F>(handler: F)
where
F: Fn(&str, &Key) + 'static,
{
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().register_input_handler(handler);
} else {
INPUT_HANDLERS.with(|handlers| {
handlers.borrow_mut().push(Rc::new(handler));
});
}
}
pub fn clear_input_handlers() {
INPUT_HANDLERS.with(|handlers| {
handlers.borrow_mut().clear();
});
}
pub fn dispatch_input(input: &str, key: &Key) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow().dispatch_input(input, key);
} else {
INPUT_HANDLERS.with(|handlers| {
for handler in handlers.borrow().iter() {
handler(input, key);
}
});
}
}
pub fn dispatch_key_event(event: &KeyEvent) {
let key = Key::from_event(event);
let input = Key::char_from_event(event);
dispatch_input(&input, &key);
}
pub fn use_input<F>(handler: F)
where
F: Fn(&str, &Key) + 'static,
{
register_input_handler(handler);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_from_event() {
let event = KeyEvent::new(KeyCode::Up, KeyModifiers::NONE);
let key = Key::from_event(&event);
assert!(key.up_arrow);
assert!(!key.down_arrow);
assert!(!key.ctrl);
}
#[test]
fn test_key_with_modifiers() {
let event = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
let key = Key::from_event(&event);
assert!(key.ctrl);
assert!(!key.shift);
}
#[test]
fn test_char_from_event() {
let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
let input = Key::char_from_event(&event);
assert_eq!(input, "a");
let event = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
let input = Key::char_from_event(&event);
assert_eq!(input, "");
}
#[test]
fn test_function_keys() {
let event = KeyEvent::new(KeyCode::F(1), KeyModifiers::NONE);
let key = Key::from_event(&event);
assert!(key.f1);
assert!(!key.f2);
let event = KeyEvent::new(KeyCode::F(12), KeyModifiers::NONE);
let key = Key::from_event(&event);
assert!(key.f12);
assert!(!key.f11);
}
#[test]
fn test_insert_key() {
let event = KeyEvent::new(KeyCode::Insert, KeyModifiers::NONE);
let key = Key::from_event(&event);
assert!(key.insert);
}
#[test]
fn test_space_key() {
let event = KeyEvent::new(KeyCode::Char(' '), KeyModifiers::NONE);
let key = Key::from_event(&event);
assert!(key.space);
}
#[test]
fn test_meta_modifier() {
let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::SUPER);
let key = Key::from_event(&event);
assert!(key.meta);
assert!(!key.ctrl);
}
#[test]
fn test_combined_modifiers() {
let event = KeyEvent::new(
KeyCode::Char('c'),
KeyModifiers::CONTROL | KeyModifiers::SHIFT,
);
let key = Key::from_event(&event);
assert!(key.ctrl);
assert!(key.shift);
assert!(!key.alt);
}
#[test]
fn test_dispatch_input_legacy() {
use std::cell::RefCell;
use std::rc::Rc;
clear_input_handlers();
let received = Rc::new(RefCell::new(String::new()));
let received_clone = received.clone();
INPUT_HANDLERS.with(|handlers| {
handlers
.borrow_mut()
.push(Rc::new(move |input: &str, _key: &Key| {
*received_clone.borrow_mut() = input.to_string();
}));
});
INPUT_HANDLERS.with(|handlers| {
for handler in handlers.borrow().iter() {
handler("test", &Key::default());
}
});
assert_eq!(*received.borrow(), "test");
clear_input_handlers();
}
#[test]
fn test_dispatch_input_with_runtime() {
use crate::runtime::{RuntimeContext, with_runtime};
use std::cell::RefCell;
use std::rc::Rc;
let ctx = Rc::new(RefCell::new(RuntimeContext::new()));
let received = Rc::new(RefCell::new(String::new()));
let received_clone = received.clone();
with_runtime(ctx.clone(), || {
use_input(move |input, _key| {
*received_clone.borrow_mut() = input.to_string();
});
});
ctx.borrow().dispatch_input("hello", &Key::default());
assert_eq!(*received.borrow(), "hello");
}
}