use color_eyre::eyre::WrapErr; use ratatui::crossterm::event::{self, Event as CrosstermEvent}; use std::{ sync::mpsc, thread, time::{Duration, Instant}, }; /// The frequency at which tick events are emitted. const TICK_FPS: f64 = 30.0; /// Representation of all possible events. #[derive(Clone, Debug)] pub enum Event { /// An event that is emitted on a regular schedule. /// /// Use this event to run any code which has to run outside of being a direct response to a user /// event. e.g. polling exernal systems, updating animations, or rendering the UI based on a /// fixed frame rate. Tick, /// Crossterm events. /// /// These events are emitted by the terminal. Crossterm(CrosstermEvent), /// Application events. /// /// Use this event to emit custom events that are specific to your application. App(AppEvent), } /// Application events. /// /// You can extend this enum with your own custom events. #[derive(Clone, Debug)] pub enum AppEvent { /// Increment the counter. Increment, /// Decrement the counter. Decrement, /// Quit the application. Quit, } /// Terminal event handler. #[derive(Debug)] pub struct EventHandler { /// Event sender channel. sender: mpsc::Sender, /// Event receiver channel. receiver: mpsc::Receiver, } impl EventHandler { /// Constructs a new instance of [`EventHandler`] and spawns a new thread to handle events. pub fn new() -> Self { let (sender, receiver) = mpsc::channel(); let actor = EventThread::new(sender.clone()); thread::spawn(|| actor.run()); Self { sender, receiver } } /// Receives an event from the sender. /// /// This function blocks until an event is received. /// /// # Errors /// /// This function returns an error if the sender channel is disconnected. This can happen if an /// error occurs in the event thread. In practice, this should not happen unless there is a /// problem with the underlying terminal. pub fn next(&self) -> color_eyre::Result { Ok(self.receiver.recv()?) } /// Queue an app event to be sent to the event receiver. /// /// This is useful for sending events to the event handler which will be processed by the next /// iteration of the application's event loop. pub fn send(&self, app_event: AppEvent) { // Ignore the result as the reciever cannot be dropped while this struct still has a // reference to it let _ = self.sender.send(Event::App(app_event)); } } /// A thread that handles reading crossterm events and emitting tick events on a regular schedule. struct EventThread { /// Event sender channel. sender: mpsc::Sender, } impl EventThread { /// Constructs a new instance of [`EventThread`]. const fn new(sender: mpsc::Sender) -> Self { Self { sender } } /// Runs the event thread. /// /// This function emits tick events at a fixed rate and polls for crossterm events in between. fn run(self) -> color_eyre::Result<()> { let tick_interval = Duration::from_secs_f64(1.0 / TICK_FPS); let mut last_tick = Instant::now(); loop { // emit tick events at a fixed rate let timeout = tick_interval.saturating_sub(last_tick.elapsed()); if timeout == Duration::ZERO { last_tick = Instant::now(); self.send(Event::Tick); } // poll for crossterm events, ensuring that we don't block the tick interval if event::poll(timeout).wrap_err("failed to poll for crossterm events")? { let event = event::read().wrap_err("failed to read crossterm event")?; self.send(Event::Crossterm(event)); } } } /// Sends an event to the receiver. fn send(&self, event: Event) { // Ignores the result because shutting down the app drops the receiver, which causes the send // operation to fail. This is expected behavior and should not panic. let _ = self.sender.send(event); } } impl Default for EventHandler { fn default() -> Self { Self::new() } }