use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AppAction {
Quit,
Next,
Previous,
Select,
Back,
OpenCommandPalette,
Copy,
Paste,
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
TabNext,
TabPrev,
FocusUp,
FocusDown,
FocusLeft,
FocusRight,
Resize,
Tick,
Custom(String),
}
#[derive(Debug, Clone)]
pub struct KeyBinding {
pub key: KeyCode,
pub modifiers: KeyModifiers,
pub action: AppAction,
pub description: String,
}
pub struct EventRouter {
bindings: Vec<KeyBinding>,
mouse_bindings: HashMap<(MouseButton, bool), AppAction>,
pending_actions: Vec<AppAction>,
}
impl EventRouter {
pub fn new() -> Self {
let mut router = Self {
bindings: Vec::new(),
mouse_bindings: HashMap::new(),
pending_actions: Vec::new(),
};
router.add_default_bindings();
router
}
fn add_default_bindings(&mut self) {
self.bind(
KeyModifiers::CONTROL,
KeyCode::Char('c'),
AppAction::Quit,
"Quit",
);
self.bind(
KeyModifiers::CONTROL,
KeyCode::Char('q'),
AppAction::Quit,
"Quit",
);
self.bind(
KeyModifiers::NONE,
KeyCode::Up,
AppAction::FocusUp,
"Focus Up",
);
self.bind(
KeyModifiers::NONE,
KeyCode::Down,
AppAction::FocusDown,
"Focus Down",
);
self.bind(
KeyModifiers::NONE,
KeyCode::Left,
AppAction::FocusLeft,
"Focus Left",
);
self.bind(
KeyModifiers::NONE,
KeyCode::Right,
AppAction::FocusRight,
"Focus Right",
);
self.bind(
KeyModifiers::NONE,
KeyCode::Tab,
AppAction::TabNext,
"Next Tab",
);
self.bind(
KeyModifiers::SHIFT,
KeyCode::BackTab,
AppAction::TabPrev,
"Previous Tab",
);
self.bind(
KeyModifiers::NONE,
KeyCode::Enter,
AppAction::Select,
"Select",
);
self.bind(KeyModifiers::NONE, KeyCode::Esc, AppAction::Back, "Back");
self.bind(
KeyModifiers::CONTROL,
KeyCode::Char('p'),
AppAction::OpenCommandPalette,
"Command Palette",
);
self.bind(
KeyModifiers::CONTROL,
KeyCode::Char('k'),
AppAction::OpenCommandPalette,
"Command Palette",
);
self.bind_key(KeyCode::PageUp, AppAction::ScrollUp, "Scroll Up");
self.bind_key(KeyCode::PageDown, AppAction::ScrollDown, "Scroll Down");
self.bind_key(KeyCode::Home, AppAction::ScrollLeft, "Scroll Start");
self.bind_key(KeyCode::End, AppAction::ScrollRight, "Scroll End");
}
pub fn bind(
&mut self,
modifiers: KeyModifiers,
key: KeyCode,
action: AppAction,
description: &str,
) {
self.bindings.push(KeyBinding {
key,
modifiers,
action,
description: description.to_string(),
});
}
pub fn bind_key(&mut self, key: KeyCode, action: AppAction, description: &str) {
self.bind(KeyModifiers::NONE, key, action, description);
}
pub fn bind_mouse(&mut self, button: MouseButton, ctrl: bool, action: AppAction) {
self.mouse_bindings.insert((button, ctrl), action);
}
pub fn handle_event(&mut self, event: &Event) -> Option<AppAction> {
match event {
Event::Key(key_event) => self.match_key(key_event),
Event::Mouse(mouse_event) => self.match_mouse(mouse_event),
Event::Resize(_, _) => Some(AppAction::Resize),
_ => None,
}
}
fn match_key(&self, key_event: &KeyEvent) -> Option<AppAction> {
for binding in &self.bindings {
if binding.key == key_event.code && binding.modifiers == key_event.modifiers {
return Some(binding.action.clone());
}
}
None
}
fn match_mouse(&self, mouse_event: &MouseEvent) -> Option<AppAction> {
let ctrl = mouse_event
.modifiers
.contains(crossterm::event::KeyModifiers::CONTROL);
match mouse_event.kind {
crossterm::event::MouseEventKind::Down(button) => {
self.mouse_bindings.get(&(button, ctrl)).cloned()
}
crossterm::event::MouseEventKind::ScrollUp => Some(AppAction::ScrollUp),
crossterm::event::MouseEventKind::ScrollDown => Some(AppAction::ScrollDown),
_ => None,
}
}
pub fn bindings(&self) -> &[KeyBinding] {
&self.bindings
}
pub fn push_action(&mut self, action: AppAction) {
self.pending_actions.push(action);
}
pub fn drain_actions(&mut self) -> Vec<AppAction> {
std::mem::take(&mut self.pending_actions)
}
}
impl Default for EventRouter {
fn default() -> Self {
Self::new()
}
}