Skip to main content

rgx/
event.rs

1use crossterm::event::{Event, EventStream, KeyEvent, KeyEventKind, MouseEvent};
2use std::time::Duration;
3use tokio::sync::mpsc;
4use tokio_stream::StreamExt;
5
6#[derive(Debug)]
7pub enum AppEvent {
8    Key(KeyEvent),
9    Mouse(MouseEvent),
10    Tick,
11    Resize(u16, u16),
12}
13
14pub struct EventHandler {
15    rx: mpsc::UnboundedReceiver<AppEvent>,
16    _task: tokio::task::JoinHandle<()>,
17}
18
19impl EventHandler {
20    pub fn new(tick_rate: Duration) -> Self {
21        let (tx, rx) = mpsc::unbounded_channel();
22
23        let task = tokio::spawn(async move {
24            let mut reader = EventStream::new();
25            let mut tick_interval = tokio::time::interval(tick_rate);
26
27            loop {
28                tokio::select! {
29                    _ = tick_interval.tick() => {
30                        if tx.send(AppEvent::Tick).is_err() {
31                            break;
32                        }
33                    }
34                    event = reader.next() => {
35                        // Translate the terminal event into an AppEvent. On Windows/WSL,
36                        // Key Release/Repeat arrive alongside Press — filter to Press only.
37                        let app_event = match event {
38                            Some(Ok(Event::Key(key))) if key.kind == KeyEventKind::Press => {
39                                AppEvent::Key(key)
40                            }
41                            Some(Ok(Event::Mouse(mouse))) => AppEvent::Mouse(mouse),
42                            Some(Ok(Event::Resize(w, h))) => AppEvent::Resize(w, h),
43                            Some(Err(_)) | None => break,
44                            _ => continue,
45                        };
46                        if tx.send(app_event).is_err() {
47                            break;
48                        }
49                    }
50                }
51            }
52        });
53
54        Self { rx, _task: task }
55    }
56
57    pub async fn next(&mut self) -> Option<AppEvent> {
58        self.rx.recv().await
59    }
60}