use std::{
sync::mpsc,
thread,
time::{Duration, Instant},
};
use color_eyre::{eyre::eyre, Result};
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
const MIN_TICK_RATE_MS: u64 = 50;
const POLL_TIMEOUT: Duration = Duration::from_millis(50);
#[derive(Clone, Copy, Debug)]
pub enum Event {
Tick,
Key(KeyEvent),
Mouse(MouseEvent),
Resize(u16, u16),
}
pub struct Communicator {
sender: mpsc::Sender<bool>,
receiver: mpsc::Receiver<Event>,
handle: thread::JoinHandle<()>,
}
impl Communicator {
pub fn new(
sender: mpsc::Sender<bool>,
receiver: mpsc::Receiver<Event>,
handle: thread::JoinHandle<()>,
) -> Self {
Self {
sender,
receiver,
handle,
}
}
}
pub struct EventHandler {
communicator: Option<Communicator>,
tick_rate: u64,
}
impl EventHandler {
pub fn new(tick_rate: u64) -> Self {
Self {
communicator: None,
tick_rate: tick_rate.max(MIN_TICK_RATE_MS),
}
}
pub fn set_tick_rate(&mut self, tick_rate: u64) {
self.tick_rate = tick_rate.max(MIN_TICK_RATE_MS);
self.stop();
self.start();
}
pub fn next(&self) -> Result<Event> {
match &self.communicator {
Some(communicator) => Ok(communicator.receiver.recv()?),
None => Err(eyre!("event handler not active")),
}
}
pub fn stop(&mut self) {
if let Some(communicator) = self.communicator.take() {
let _ = communicator.sender.send(true);
let _ = communicator.handle.join();
}
}
pub fn start(&mut self) {
self.stop();
let tick_rate = Duration::from_millis(self.tick_rate);
let (sender, receiver) = mpsc::channel();
let (stop_sender, stop_receiver) = mpsc::channel::<bool>();
let handle = thread::spawn(move || {
let mut last_tick = Instant::now();
while stop_receiver.try_recv().is_err() {
let event_available = match event::poll(POLL_TIMEOUT) {
Ok(available) => available,
Err(_) => break,
};
if event_available {
let crossterm_event = match event::read() {
Ok(crossterm_event) => crossterm_event,
Err(_) => break,
};
let send_result = match crossterm_event {
CrosstermEvent::Key(e) => {
if e.kind == event::KeyEventKind::Press {
sender.send(Event::Key(e))
} else {
Ok(()) }
}
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
_ => Ok(()),
};
if send_result.is_err() {
break;
}
}
if last_tick.elapsed() >= tick_rate {
let send_result = sender.send(Event::Tick);
if send_result.is_err() {
break;
}
last_tick = Instant::now();
}
}
});
let communicator = Communicator::new(stop_sender, receiver, handle);
self.communicator = Some(communicator);
}
}