Handle results
This commit is contained in:
parent
47aa6d2e96
commit
9fd09e9db2
@ -57,13 +57,17 @@ fn main() {
|
|||||||
// Initialize the terminal user interface.
|
// Initialize the terminal user interface.
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
let terminal = Terminal::new(backend).expect("failed to initialise terminal");
|
let terminal = Terminal::new(backend).expect("failed to initialise terminal");
|
||||||
|
|
||||||
let channel = EventChannel::new();
|
let channel = EventChannel::new();
|
||||||
let listener = EventListener::new(channel.sender());
|
let listener = EventListener::new(channel.sender());
|
||||||
let handler = EventHandler::new(channel.receiver());
|
let handler = EventHandler::new(channel.receiver());
|
||||||
|
|
||||||
let ui = Ui::new();
|
let ui = Ui::new();
|
||||||
|
|
||||||
let app = App::new(collection_manager).expect("failed to initialise app");
|
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.
|
// 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 crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
|
||||||
use std::sync::mpsc;
|
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 {
|
pub enum Event {
|
||||||
Key(KeyEvent),
|
Key(KeyEvent),
|
||||||
Mouse(MouseEvent),
|
Mouse(MouseEvent),
|
||||||
@ -43,44 +74,46 @@ impl EventChannel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventSender {
|
impl EventSender {
|
||||||
pub fn send(&self, event: Event) {
|
pub fn send(&self, event: Event) -> Result<(), EventError> {
|
||||||
self.sender
|
Ok(self.sender.send(event)?)
|
||||||
.send(event)
|
|
||||||
.expect("failed to send terminal event");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventReceiver {
|
impl EventReceiver {
|
||||||
pub fn recv(&self) -> Event {
|
pub fn recv(&self) -> Result<Event, EventError> {
|
||||||
self.receiver
|
Ok(self.receiver.recv()?)
|
||||||
.recv()
|
|
||||||
.expect("failed to receive terminal event")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventListener {
|
pub struct EventListener {
|
||||||
events: Option<EventSender>,
|
events: EventSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventListener {
|
impl EventListener {
|
||||||
pub fn new(events: EventSender) -> EventListener {
|
pub fn new(events: EventSender) -> EventListener {
|
||||||
EventListener {
|
EventListener { events }
|
||||||
events: Some(events),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(&mut self) {
|
pub fn spawn(self) -> thread::JoinHandle<EventError> {
|
||||||
let sender = self.events.take().unwrap();
|
thread::spawn(move || {
|
||||||
thread::spawn(move || loop {
|
loop {
|
||||||
// Put this inside an if event::poll {...} if the display needs to be refreshed on a
|
// Put this inside an if event::poll {...} if the display needs to be refreshed on a
|
||||||
// periodic basis. See
|
// periodic basis. See
|
||||||
// https://github.com/tui-rs-revival/rust-tui-template/blob/master/src/event.rs.
|
// https://github.com/tui-rs-revival/rust-tui-template/blob/master/src/event.rs.
|
||||||
match event::read().expect("unable to read event") {
|
match event::read() {
|
||||||
CrosstermEvent::Key(e) => sender.send(Event::Key(e)),
|
Ok(event) => {
|
||||||
CrosstermEvent::Mouse(e) => sender.send(Event::Mouse(e)),
|
if let Err(err) = match event {
|
||||||
CrosstermEvent::Resize(w, h) => sender.send(Event::Resize(w, h)),
|
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!(),
|
_ => unimplemented!(),
|
||||||
|
} {
|
||||||
|
return err;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
Err(err) => return EventError::IoError(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
use super::{app::App, event::{Event, EventReceiver}};
|
use super::{app::App, event::{Event, EventReceiver, EventError}};
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
events: EventReceiver,
|
events: EventReceiver,
|
||||||
@ -11,12 +11,13 @@ impl EventHandler {
|
|||||||
EventHandler { events }
|
EventHandler { events }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_next_event(&mut self, app: &mut App) {
|
pub fn handle_next_event(&mut self, app: &mut App) -> Result<(), EventError> {
|
||||||
match self.events.recv() {
|
match self.events.recv()? {
|
||||||
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
Event::Key(key_event) => Self::handle_key_event(app, key_event),
|
||||||
Event::Mouse(_) => {}
|
Event::Mouse(_) => {}
|
||||||
Event::Resize(_, _) => {}
|
Event::Resize(_, _) => {}
|
||||||
}
|
};
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_key_event(app: &mut App, key_event: KeyEvent) {
|
fn handle_key_event(app: &mut App, key_event: KeyEvent) {
|
||||||
|
@ -11,13 +11,16 @@ pub mod handler;
|
|||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
use self::app::App;
|
use self::app::App;
|
||||||
use self::event::EventListener;
|
use self::event::{EventError, EventListener};
|
||||||
use self::handler::EventHandler;
|
use self::handler::EventHandler;
|
||||||
use self::ui::Ui;
|
use self::ui::Ui;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
CollectionError(String),
|
CollectionError(String),
|
||||||
|
IoError(String),
|
||||||
|
EventError(String),
|
||||||
|
ListenerPanic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<collection::Error> for Error {
|
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> {
|
pub struct Tui<B: Backend> {
|
||||||
terminal: Terminal<B>,
|
terminal: Terminal<B>,
|
||||||
listener: EventListener,
|
listener: Option<EventListener>,
|
||||||
handler: EventHandler,
|
handler: EventHandler,
|
||||||
ui: Ui,
|
ui: Ui,
|
||||||
app: App,
|
app: App,
|
||||||
@ -44,37 +59,71 @@ impl<B: Backend> Tui<B> {
|
|||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
terminal,
|
terminal,
|
||||||
listener,
|
listener: Some(listener),
|
||||||
handler,
|
handler,
|
||||||
ui,
|
ui,
|
||||||
app,
|
app,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(&mut self) {
|
fn init(&mut self) -> Result<(), Error> {
|
||||||
terminal::enable_raw_mode().unwrap();
|
terminal::enable_raw_mode()?;
|
||||||
crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture).unwrap();
|
crossterm::execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
self.terminal.hide_cursor().unwrap();
|
self.terminal.hide_cursor()?;
|
||||||
self.terminal.clear().unwrap();
|
self.terminal.clear()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self) {
|
fn run_loop(&mut self) -> Result<(), Error> {
|
||||||
self.init();
|
|
||||||
|
|
||||||
self.listener.spawn();
|
|
||||||
while self.app.is_running() {
|
while self.app.is_running() {
|
||||||
self.terminal
|
self.terminal
|
||||||
.draw(|frame| self.ui.render(&mut self.app, frame))
|
.draw(|frame| self.ui.render(&mut self.app, frame))?;
|
||||||
.unwrap();
|
self.handler.handle_next_event(&mut self.app)?;
|
||||||
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();
|
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