mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-20 11:04:38 +00:00
132 lines
4.2 KiB
Rust
132 lines
4.2 KiB
Rust
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>,
|
|
/// Event receiver channel.
|
|
receiver: mpsc::Receiver<Event>,
|
|
}
|
|
|
|
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<Event> {
|
|
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<Event>,
|
|
}
|
|
|
|
impl EventThread {
|
|
/// Constructs a new instance of [`EventThread`].
|
|
const fn new(sender: mpsc::Sender<Event>) -> 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()
|
|
}
|
|
}
|