use anyhow::Result;
use crossterm::event::{
self, DisableMouseCapture, EnableMouseCapture, Event as CrosstermEvent,
KeyCode, KeyEvent, KeyModifiers, MouseEvent,
};
use std::time::Duration;
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
Resize(u16, u16),
Tick,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Key {
Up,
Down,
Left,
Right,
Enter,
Esc,
Backspace,
Delete,
Char(char),
Ctrl(char),
Alt(char),
F(u8),
Tab,
BackTab,
Unknown,
}
impl From<KeyEvent> for Key {
fn from(key_event: KeyEvent) -> Self {
match key_event.code {
KeyCode::Up => Key::Up,
KeyCode::Down => Key::Down,
KeyCode::Left => Key::Left,
KeyCode::Right => Key::Right,
KeyCode::Enter => Key::Enter,
KeyCode::Esc => Key::Esc,
KeyCode::Backspace => Key::Backspace,
KeyCode::Delete => Key::Delete,
KeyCode::Tab => Key::Tab,
KeyCode::BackTab => Key::BackTab,
KeyCode::F(n) => Key::F(n),
KeyCode::Char(c) => {
if key_event.modifiers.contains(KeyModifiers::CONTROL) {
Key::Ctrl(c)
} else if key_event.modifiers.contains(KeyModifiers::ALT) {
Key::Alt(c)
} else {
Key::Char(c)
}
}
_ => Key::Unknown,
}
}
}
pub struct EventHandler {
tx: mpsc::UnboundedSender<Event>,
rx: mpsc::UnboundedReceiver<Event>,
tick_rate: Duration,
}
impl EventHandler {
pub fn new(tick_rate_ms: u64) -> Self {
let tick_rate = Duration::from_millis(tick_rate_ms);
let (tx, rx) = mpsc::unbounded_channel();
Self {
tx,
rx,
tick_rate,
}
}
pub fn start(&self) {
let tx = self.tx.clone();
let tick_rate = self.tick_rate;
tokio::spawn(async move {
let mut last_tick = tokio::time::Instant::now();
loop {
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or(Duration::from_secs(0));
if event::poll(timeout).unwrap_or(false) {
match event::read() {
Ok(CrosstermEvent::Key(key)) => {
if tx.send(Event::Key(key)).is_err() {
break;
}
}
Ok(CrosstermEvent::Mouse(mouse)) => {
if tx.send(Event::Mouse(mouse)).is_err() {
break;
}
}
Ok(CrosstermEvent::Resize(w, h)) => {
if tx.send(Event::Resize(w, h)).is_err() {
break;
}
}
Ok(_) => {}
Err(_) => break,
}
}
if last_tick.elapsed() >= tick_rate {
if tx.send(Event::Tick).is_err() {
break;
}
last_tick = tokio::time::Instant::now();
}
}
});
}
pub async fn next(&mut self) -> Option<Event> {
self.rx.recv().await
}
pub fn try_next(&mut self) -> Option<Event> {
self.rx.try_recv().ok()
}
}
pub fn enable_mouse_capture() -> Result<()> {
crossterm::execute!(std::io::stdout(), EnableMouseCapture)?;
Ok(())
}
pub fn disable_mouse_capture() -> Result<()> {
crossterm::execute!(std::io::stdout(), DisableMouseCapture)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_key_from_event() {
let key_event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
let key: Key = key_event.into();
assert_eq!(key, Key::Char('a'));
let key_event = KeyEvent::new(KeyCode::Char('c'), KeyModifiers::CONTROL);
let key: Key = key_event.into();
assert_eq!(key, Key::Ctrl('c'));
let key_event = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
let key: Key = key_event.into();
assert_eq!(key, Key::Enter);
}
#[tokio::test]
async fn test_event_handler_creation() {
let handler = EventHandler::new(250);
assert_eq!(handler.tick_rate, Duration::from_millis(250));
}
}