use anyhow::Result;
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, KeyEventKind, MouseEvent};
use log::debug;
use std::cell::RefCell;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Sender};
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use crate::store;
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
Resize(u16, u16),
Store(store::Event),
}
#[derive(Debug)]
pub struct EventBus {
sender: mpsc::Sender<Event>,
receiver: mpsc::Receiver<Event>,
running: Arc<AtomicBool>,
handles: RefCell<Vec<thread::JoinHandle<()>>>,
}
impl Default for EventBus {
fn default() -> Self {
Self::new()
}
}
impl EventBus {
pub fn new() -> Self {
let (sender, receiver) = mpsc::channel();
Self {
sender,
receiver,
running: Arc::new(AtomicBool::new(true)),
handles: Default::default(),
}
}
pub fn next(&self) -> Result<Event> {
Ok(self.receiver.recv()?)
}
pub fn spawn<F>(&self, name: impl ToString, f: F)
where
F: 'static + Send + FnOnce(Arc<AtomicBool>, Sender<Event>),
{
let sender = self.sender.clone();
let running = self.running.clone();
self.handles.borrow_mut().push(
thread::Builder::new()
.name(name.to_string())
.spawn(move || f(running, sender))
.unwrap(),
);
}
pub fn spawn_terminal_listener(&self) {
self.spawn("terminal_events", Self::terminal_events)
}
fn terminal_events(running: Arc<AtomicBool>, sender: Sender<Event>) {
loop {
if event::poll(Duration::from_millis(250)).expect("unable to poll for events") {
match event::read().expect("unable to read event") {
CrosstermEvent::Key(e) if e.kind == KeyEventKind::Press => {
sender.send(Event::Key(e))
}
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
_ => Ok(()),
}
.expect("failed to send terminal event");
}
if !running.load(Ordering::Relaxed) {
break;
}
}
}
}
impl Drop for EventBus {
fn drop(&mut self) {
self.running.store(false, Ordering::Relaxed);
self.handles.borrow_mut().drain(..).for_each(|h| {
debug!("joining thread {:?}", h.thread().name());
h.join().unwrap()
});
}
}