use crate::app::AppResult; use crate::backend::event::BackendEvent; use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent}; use std::sync::mpsc; use std::thread; use std::time::{Duration, Instant}; /// Terminal events. #[derive(Clone, Debug)] pub enum Event { /// Terminal tick. Tick, /// Key press. Key(KeyEvent), /// Mouse click/scroll. Mouse(MouseEvent), /// Terminal resize. Resize(u16, u16), Backend(BackendEvent), } /// Terminal event handler. #[allow(dead_code)] #[derive(Debug)] pub struct EventHandler { /// Event sender channel. sender: mpsc::Sender, /// Event receiver channel. receiver: mpsc::Receiver, /// Event handler thread. handler: thread::JoinHandle<()>, } impl EventHandler { /// Constructs a new instance of [`EventHandler`]. pub fn new(tick_rate: u64) -> Self { let tick_rate = Duration::from_millis(tick_rate); let (sender, receiver) = mpsc::channel(); let handler = { let sender = sender.clone(); thread::spawn(move || { let mut last_tick = Instant::now(); loop { let timeout = tick_rate .checked_sub(last_tick.elapsed()) .unwrap_or(tick_rate); if event::poll(timeout).expect("no events available") { match event::read().expect("unable to read event") { CrosstermEvent::Key(e) => sender.send(Event::Key(e)), CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)), CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)), _ => unimplemented!(), } .expect("failed to send terminal event") } if last_tick.elapsed() >= tick_rate { sender.send(Event::Tick).expect("failed to send tick event"); last_tick = Instant::now(); } } }) }; Self { sender, receiver, handler, } } /// Receive the next event from the handler thread. /// /// This function will always block the current thread if /// there is no data available and it's possible for more data to be sent. pub fn next(&self) -> AppResult { Ok(self.receiver.recv()?) } pub fn sender(&self) -> mpsc::Sender { self.sender.clone() } }