Handle results
This commit is contained in:
parent
47aa6d2e96
commit
9fd09e9db2
@ -57,13 +57,17 @@ fn main() {
|
||||
// Initialize the terminal user interface.
|
||||
let backend = CrosstermBackend::new(io::stdout());
|
||||
let terminal = Terminal::new(backend).expect("failed to initialise terminal");
|
||||
|
||||
let channel = EventChannel::new();
|
||||
let listener = EventListener::new(channel.sender());
|
||||
let handler = EventHandler::new(channel.receiver());
|
||||
|
||||
let ui = Ui::new();
|
||||
|
||||
let app = App::new(collection_manager).expect("failed to initialise app");
|
||||
let mut tui = Tui::new(terminal, listener, handler, ui, app);
|
||||
|
||||
let tui = Tui::new(terminal, listener, handler, ui, app);
|
||||
|
||||
// Run the TUI application.
|
||||
tui.run();
|
||||
tui.run().expect("failed to run tui");
|
||||
}
|
||||
|
@ -1,8 +1,39 @@
|
||||
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::{fmt, thread};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Debug)]
|
||||
pub enum EventError {
|
||||
SendError(Event),
|
||||
RecvError,
|
||||
IoError(std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for EventError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Self::SendError(ref e) => write!(f, "failed to send event: {e:?}"),
|
||||
Self::RecvError => write!(f, "receive event call failed"),
|
||||
Self::IoError(ref e) => {
|
||||
write!(f, "an I/O error was triggered during event handling: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpsc::SendError<Event>> for EventError {
|
||||
fn from(err: mpsc::SendError<Event>) -> EventError {
|
||||
EventError::SendError(err.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<mpsc::RecvError> for EventError {
|
||||
fn from(_: mpsc::RecvError) -> EventError {
|
||||
EventError::RecvError
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Event {
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
@ -43,44 +74,46 @@ impl EventChannel {
|
||||
}
|
||||
|
||||
impl EventSender {
|
||||
pub fn send(&self, event: Event) {
|
||||
self.sender
|
||||
.send(event)
|
||||
.expect("failed to send terminal event");
|
||||
pub fn send(&self, event: Event) -> Result<(), EventError> {
|
||||
Ok(self.sender.send(event)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventReceiver {
|
||||
pub fn recv(&self) -> Event {
|
||||
self.receiver
|
||||
.recv()
|
||||
.expect("failed to receive terminal event")
|
||||
pub fn recv(&self) -> Result<Event, EventError> {
|
||||
Ok(self.receiver.recv()?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventListener {
|
||||
events: Option<EventSender>,
|
||||
events: EventSender,
|
||||
}
|
||||
|
||||
impl EventListener {
|
||||
pub fn new(events: EventSender) -> EventListener {
|
||||
EventListener {
|
||||
events: Some(events),
|
||||
}
|
||||
EventListener { events }
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) {
|
||||
let sender = self.events.take().unwrap();
|
||||
thread::spawn(move || loop {
|
||||
pub fn spawn(self) -> thread::JoinHandle<EventError> {
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
// Put this inside an if event::poll {...} if the display needs to be refreshed on a
|
||||
// periodic basis. See
|
||||
// https://github.com/tui-rs-revival/rust-tui-template/blob/master/src/event.rs.
|
||||
match event::read().expect("unable to read event") {
|
||||
CrosstermEvent::Key(e) => sender.send(Event::Key(e)),
|
||||
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
|
||||
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
|
||||
match event::read() {
|
||||
Ok(event) => {
|
||||
if let Err(err) = match event {
|
||||
CrosstermEvent::Key(e) => self.events.send(Event::Key(e)),
|
||||
CrosstermEvent::Mouse(e) => self.events.send(Event::Mouse(e)),
|
||||
CrosstermEvent::Resize(w, h) => self.events.send(Event::Resize(w, h)),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
});
|
||||
} {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
Err(err) => return EventError::IoError(err),
|
||||
};
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
|
||||
use super::{app::App, event::{Event, EventReceiver}};
|
||||
use super::{app::App, event::{Event, EventReceiver, EventError}};
|
||||
|
||||
pub struct EventHandler {
|
||||
events: EventReceiver,
|
||||
@ -11,12 +11,13 @@ impl EventHandler {
|
||||
EventHandler { events }
|
||||
}
|
||||
|
||||
pub fn handle_next_event(&mut self, app: &mut App) {
|
||||
match self.events.recv() {
|
||||
pub fn handle_next_event(&mut self, app: &mut App) -> Result<(), EventError> {
|
||||
match self.events.recv()? {
|
||||
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
||||
Event::Mouse(_) => {}
|
||||
Event::Resize(_, _) => {}
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_key_event(app: &mut App, key_event: KeyEvent) {
|
||||
|
@ -11,13 +11,16 @@ pub mod handler;
|
||||
pub mod ui;
|
||||
|
||||
use self::app::App;
|
||||
use self::event::EventListener;
|
||||
use self::event::{EventError, EventListener};
|
||||
use self::handler::EventHandler;
|
||||
use self::ui::Ui;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
CollectionError(String),
|
||||
IoError(String),
|
||||
EventError(String),
|
||||
ListenerPanic,
|
||||
}
|
||||
|
||||
impl From<collection::Error> for Error {
|
||||
@ -26,9 +29,21 @@ impl From<collection::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::IoError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventError> for Error {
|
||||
fn from(err: EventError) -> Error {
|
||||
Error::EventError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Tui<B: Backend> {
|
||||
terminal: Terminal<B>,
|
||||
listener: EventListener,
|
||||
listener: Option<EventListener>,
|
||||
handler: EventHandler,
|
||||
ui: Ui,
|
||||
app: App,
|
||||
@ -44,37 +59,71 @@ impl<B: Backend> Tui<B> {
|
||||
) -> Self {
|
||||
Self {
|
||||
terminal,
|
||||
listener,
|
||||
listener: Some(listener),
|
||||
handler,
|
||||
ui,
|
||||
app,
|
||||
}
|
||||
}
|
||||
|
||||
fn init(&mut self) {
|
||||
terminal::enable_raw_mode().unwrap();
|
||||
crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture).unwrap();
|
||||
self.terminal.hide_cursor().unwrap();
|
||||
self.terminal.clear().unwrap();
|
||||
fn init(&mut self) -> Result<(), Error> {
|
||||
terminal::enable_raw_mode()?;
|
||||
crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
|
||||
self.terminal.hide_cursor()?;
|
||||
self.terminal.clear()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
self.init();
|
||||
|
||||
self.listener.spawn();
|
||||
fn run_loop(&mut self) -> Result<(), Error> {
|
||||
while self.app.is_running() {
|
||||
self.terminal
|
||||
.draw(|frame| self.ui.render(&mut self.app, frame))
|
||||
.unwrap();
|
||||
self.handler.handle_next_event(&mut self.app);
|
||||
.draw(|frame| self.ui.render(&mut self.app, frame))?;
|
||||
self.handler.handle_next_event(&mut self.app)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(mut self) -> Result<(), Error> {
|
||||
self.init()?;
|
||||
|
||||
let listener_handle = self.listener.take().unwrap().spawn();
|
||||
let result = self.run_loop();
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
self.exit()?;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
// We want to call exit before handling the run_loop result to reset the terminal.
|
||||
// Therefore, we suppress exit errors (if any) to not mask the original error.
|
||||
self.exit_suppress_errors();
|
||||
|
||||
if listener_handle.is_finished() {
|
||||
match listener_handle.join() {
|
||||
Ok(err) => return Err(err.into()),
|
||||
// Calling std::panic::resume_unwind(err) as recommended by the Rust docs
|
||||
// will not produce an error message. The panic error message is printed at
|
||||
// the location of the panic which at the time is hidden by the TUI.
|
||||
Err(_) => return Err(Error::ListenerPanic),
|
||||
}
|
||||
}
|
||||
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn exit(&mut self) -> Result<(), Error> {
|
||||
terminal::disable_raw_mode()?;
|
||||
crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?;
|
||||
self.terminal.show_cursor()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
fn exit_suppress_errors(&mut self) {
|
||||
self.exit();
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
terminal::disable_raw_mode().unwrap();
|
||||
crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
|
||||
self.terminal.show_cursor().unwrap();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user