use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
#[derive(Debug, Clone)]
pub enum AppEvent {
Key(KeyEvent),
Mouse(MouseEvent),
Tick,
Resize(u16, u16),
}
pub struct EventHandler {
rx: mpsc::UnboundedReceiver<AppEvent>,
#[allow(dead_code)]
tx: mpsc::UnboundedSender<AppEvent>,
}
impl EventHandler {
pub fn new(tick_rate: Duration) -> Self {
let (tx, rx) = mpsc::unbounded_channel();
let event_tx = tx.clone();
tokio::spawn(async move {
let mut last_tick = Instant::now();
loop {
let timeout = tick_rate.saturating_sub(last_tick.elapsed());
if event::poll(timeout).unwrap_or(false) {
match event::read() {
Ok(CrosstermEvent::Key(key)) => {
if event_tx.send(AppEvent::Key(key)).is_err() {
break;
}
}
Ok(CrosstermEvent::Mouse(mouse)) => {
if event_tx.send(AppEvent::Mouse(mouse)).is_err() {
break;
}
}
Ok(CrosstermEvent::Resize(width, height)) => {
if event_tx.send(AppEvent::Resize(width, height)).is_err() {
break;
}
}
Ok(CrosstermEvent::FocusGained | CrosstermEvent::FocusLost) => {
}
Ok(CrosstermEvent::Paste(_)) => {
}
Err(_) => {
}
}
}
if last_tick.elapsed() >= tick_rate {
if event_tx.send(AppEvent::Tick).is_err() {
break;
}
last_tick = Instant::now();
}
}
});
Self { rx, tx }
}
pub async fn next(&mut self) -> Option<AppEvent> {
self.rx.recv().await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_event_variants() {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
let key = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::NONE);
let event = AppEvent::Key(key);
assert!(matches!(event, AppEvent::Key(_)));
let mouse = MouseEvent {
kind: MouseEventKind::Down(MouseButton::Left),
column: 0,
row: 0,
modifiers: KeyModifiers::NONE,
};
let event = AppEvent::Mouse(mouse);
assert!(matches!(event, AppEvent::Mouse(_)));
let event = AppEvent::Tick;
assert!(matches!(event, AppEvent::Tick));
let event = AppEvent::Resize(80, 24);
assert!(matches!(event, AppEvent::Resize(80, 24)));
}
#[test]
fn test_app_event_clone() {
let event = AppEvent::Tick;
let cloned = event.clone();
assert!(matches!(cloned, AppEvent::Tick));
let event = AppEvent::Resize(100, 50);
let cloned = event.clone();
assert!(matches!(cloned, AppEvent::Resize(100, 50)));
}
#[tokio::test]
async fn test_event_handler_creation() {
let _handler = EventHandler::new(Duration::from_millis(100));
}
}