Skip to main content

imp_tui/
event_source.rs

1use std::sync::{
2    atomic::{AtomicBool, Ordering},
3    Arc,
4};
5use std::thread;
6use std::time::Duration;
7
8use crossterm::event::{self, Event};
9use tokio::sync::mpsc;
10
11const INPUT_POLL_INTERVAL: Duration = Duration::from_millis(10);
12const TERMINAL_EVENT_CHANNEL_CAPACITY: usize = 1024;
13
14/// Owns the blocking crossterm input reader thread and forwards terminal events
15/// to the async TUI loop.
16pub struct TerminalEventSource {
17    stop: Arc<AtomicBool>,
18    thread: Option<thread::JoinHandle<()>>,
19}
20
21impl TerminalEventSource {
22    pub fn spawn() -> (Self, mpsc::Receiver<Event>) {
23        let (tx, rx) = mpsc::channel(TERMINAL_EVENT_CHANNEL_CAPACITY);
24        let stop = Arc::new(AtomicBool::new(false));
25        let thread_stop = Arc::clone(&stop);
26
27        let thread = thread::spawn(move || {
28            while !thread_stop.load(Ordering::Relaxed) {
29                match event::poll(INPUT_POLL_INTERVAL) {
30                    Ok(true) => match event::read() {
31                        Ok(event) => {
32                            if tx.blocking_send(event).is_err() {
33                                break;
34                            }
35                        }
36                        Err(_) => break,
37                    },
38                    Ok(false) => {}
39                    Err(_) => break,
40                }
41            }
42        });
43
44        (
45            Self {
46                stop,
47                thread: Some(thread),
48            },
49            rx,
50        )
51    }
52}
53
54impl Drop for TerminalEventSource {
55    fn drop(&mut self) {
56        self.stop.store(true, Ordering::Relaxed);
57        if let Some(thread) = self.thread.take() {
58            let _ = thread.join();
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn terminal_event_source_can_be_dropped_without_events() {
69        let (source, _rx) = TerminalEventSource::spawn();
70        drop(source);
71    }
72}