start work on tui app and rework secrets system
This commit is contained in:
125
src/event.rs
Normal file
125
src/event.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use color_eyre::eyre::OptionExt;
|
||||
use color_eyre::Result;
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use ratatui::crossterm::event::Event as CrosstermEvent;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::{self, UnboundedSender};
|
||||
|
||||
use crate::manifest::Manifest;
|
||||
|
||||
/// The frequency at which tick events are emitted.
|
||||
const TICK_FPS: f64 = 30.0;
|
||||
|
||||
/// Representation of all possible events.
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Tick,
|
||||
Crossterm(CrosstermEvent),
|
||||
App(AppEvent),
|
||||
}
|
||||
|
||||
/// Application events.
|
||||
///
|
||||
/// You can extend this enum with your own custom events.
|
||||
#[derive(Debug)]
|
||||
pub enum AppEvent {
|
||||
SelectionUp,
|
||||
SelectionDown,
|
||||
LoadManifest,
|
||||
ManifestLoaded(Result<Manifest>),
|
||||
/// Quit the application.
|
||||
Quit,
|
||||
}
|
||||
|
||||
/// Terminal event handler.
|
||||
#[derive(Debug)]
|
||||
pub struct EventHandler {
|
||||
/// Event sender channel.
|
||||
sender: mpsc::UnboundedSender<Event>,
|
||||
/// Event receiver channel.
|
||||
receiver: mpsc::UnboundedReceiver<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::unbounded_channel();
|
||||
let actor = EventTask::new(sender.clone());
|
||||
tokio::spawn(async { actor.run().await });
|
||||
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 async fn next(&mut self) -> color_eyre::Result<Event> {
|
||||
self.receiver
|
||||
.recv()
|
||||
.await
|
||||
.ok_or_eyre("Failed to receive event")
|
||||
}
|
||||
|
||||
/// 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(&mut 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));
|
||||
}
|
||||
pub fn clone_sender(&self) -> UnboundedSender<Event> {
|
||||
self.sender.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// A thread that handles reading crossterm events and emitting tick events on a regular schedule.
|
||||
struct EventTask {
|
||||
/// Event sender channel.
|
||||
sender: mpsc::UnboundedSender<Event>,
|
||||
}
|
||||
|
||||
impl EventTask {
|
||||
/// Constructs a new instance of [`EventThread`].
|
||||
fn new(sender: mpsc::UnboundedSender<Event>) -> Self {
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
/// Runs the event thread.
|
||||
///
|
||||
/// This function emits tick events at a fixed rate and polls for crossterm events in between.
|
||||
async fn run(self) -> color_eyre::Result<()> {
|
||||
let tick_rate = Duration::from_secs_f64(1.0 / TICK_FPS);
|
||||
let mut reader = crossterm::event::EventStream::new();
|
||||
let mut tick = tokio::time::interval(tick_rate);
|
||||
loop {
|
||||
let tick_delay = tick.tick();
|
||||
let crossterm_event = reader.next().fuse();
|
||||
tokio::select! {
|
||||
_ = self.sender.closed() => {
|
||||
break;
|
||||
}
|
||||
_ = tick_delay => {
|
||||
self.send(Event::Tick);
|
||||
}
|
||||
Some(Ok(evt)) = crossterm_event => {
|
||||
self.send(Event::Crossterm(evt));
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user