1use crossterm::event::{Event as CrosstermEvent, KeyEvent, KeyEventKind};
2use futures::FutureExt;
3use tokio::{sync::mpsc, task::JoinHandle};
4use tokio_stream::StreamExt;
5use tokio_util::sync::CancellationToken;
6use tracing::warn;
7
8#[derive(Clone, Copy, Debug)]
9pub enum Event {
10 Quit,
11 RenderTick,
12 Key(KeyEvent),
13 Resize(u16, u16),
14}
15
16#[derive(Debug)]
17pub struct EventHandler {
18 rx: mpsc::UnboundedReceiver<Event>,
19 task: Option<JoinHandle<()>>,
20 stop_cancellation_token: CancellationToken,
21}
22
23impl EventHandler {
24 pub fn new(render_tick_rate: u64) -> Self {
25 let render_tick_rate = std::time::Duration::from_millis(render_tick_rate);
26
27 let (event_tx, event_rx) = mpsc::unbounded_channel();
28
29 let stop_cancellation_token = CancellationToken::new();
30 let _stop_cancellation_token = stop_cancellation_token.clone();
31
32 let task = tokio::spawn(async move {
33 let mut reader = crossterm::event::EventStream::new();
34 let mut render_interval = tokio::time::interval(render_tick_rate);
35
36 loop {
37 let render_delay = render_interval.tick();
38 let crossterm_event = reader.next().fuse();
39 tokio::select! {
40 _ = _stop_cancellation_token.cancelled() => break,
41 maybe_event = crossterm_event => match maybe_event {
42 Some(Ok(evt)) => match evt {
43 CrosstermEvent::Key(key) => if key.kind == KeyEventKind::Press {
44 event_tx.send(Event::Key(key)).unwrap();
45 },
46 CrosstermEvent::Resize(x, y) => {
47 event_tx.send(Event::Resize(x, y)).unwrap();
48 },
49 _ => {}
50 }
51 Some(Err(error)) => {
52 warn!("crossterm event error: {error:?}");
53 }
54 None => {}
55 },
56 _ = render_delay => event_tx.send(Event::RenderTick).unwrap(),
57 }
58 }
59 });
60
61 Self {
62 rx: event_rx,
63 task: Some(task),
64 stop_cancellation_token,
65 }
66 }
67
68 pub async fn next(&mut self) -> Option<Event> {
69 self.rx.recv().await
70 }
71
72 pub async fn stop(&mut self) {
73 self.stop_cancellation_token.cancel();
74 if let Some(handle) = self.task.take() {
75 handle.await.unwrap();
76 }
77 }
78}