Skip to main content

wiki_tui/
event.rs

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}