Add shortcut to reload database and/or library #116
@ -67,7 +67,7 @@ fn with<LIB: ILibrary, DB: IDatabase>(builder: MusicHoardBuilder<LIB, DB>) {
|
|||||||
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 app = App::new(music_hoard).expect("failed to initialise application");
|
let app = App::new(music_hoard);
|
||||||
let ui = Ui;
|
let ui = Ui;
|
||||||
|
|
||||||
// Run the TUI application.
|
// Run the TUI application.
|
||||||
|
164
src/tui/app.rs
164
src/tui/app.rs
@ -1,28 +1,7 @@
|
|||||||
use std::fmt;
|
|
||||||
|
|
||||||
use musichoard::collection::{album::Album, artist::Artist, track::Track, Collection};
|
use musichoard::collection::{album::Album, artist::Artist, track::Track, Collection};
|
||||||
use ratatui::widgets::ListState;
|
use ratatui::widgets::ListState;
|
||||||
|
|
||||||
use crate::tui::{lib::IMusicHoard, Error};
|
use crate::tui::lib::IMusicHoard;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AppError {
|
|
||||||
Lib(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for AppError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match *self {
|
|
||||||
Self::Lib(ref s) => write!(f, "the musichoard library returned an error: {s}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<musichoard::Error> for AppError {
|
|
||||||
fn from(err: musichoard::Error) -> AppError {
|
|
||||||
AppError::Lib(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AppState<BS, IS, RS, ES> {
|
pub enum AppState<BS, IS, RS, ES> {
|
||||||
Browse(BS),
|
Browse(BS),
|
||||||
@ -57,8 +36,9 @@ pub trait IAppInteract {
|
|||||||
|
|
||||||
fn is_running(&self) -> bool;
|
fn is_running(&self) -> bool;
|
||||||
fn quit(&mut self);
|
fn quit(&mut self);
|
||||||
|
fn force_quit(&mut self);
|
||||||
|
|
||||||
fn save(&mut self) -> Result<(), AppError>;
|
fn save(&mut self);
|
||||||
|
|
||||||
fn state(&mut self) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES>;
|
fn state(&mut self) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES>;
|
||||||
}
|
}
|
||||||
@ -337,17 +317,24 @@ pub struct App<MH: IMusicHoard> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<MH: IMusicHoard> App<MH> {
|
impl<MH: IMusicHoard> App<MH> {
|
||||||
pub fn new(mut music_hoard: MH) -> Result<Self, Error> {
|
pub fn new(mut music_hoard: MH) -> Self {
|
||||||
// FIXME: if either returns an error start in an error state
|
let state = match Self::init(&mut music_hoard) {
|
||||||
music_hoard.load_from_database()?;
|
Ok(()) => AppState::Browse(()),
|
||||||
music_hoard.rescan_library()?;
|
Err(err) => AppState::Error(err.to_string()),
|
||||||
|
};
|
||||||
let selection = Selection::new(Some(music_hoard.get_collection()));
|
let selection = Selection::new(Some(music_hoard.get_collection()));
|
||||||
Ok(App {
|
App {
|
||||||
running: true,
|
running: true,
|
||||||
music_hoard,
|
music_hoard,
|
||||||
selection,
|
selection,
|
||||||
state: AppState::Browse(()),
|
state,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(music_hoard: &mut MH) -> Result<(), musichoard::Error> {
|
||||||
|
music_hoard.load_from_database()?;
|
||||||
|
music_hoard.rescan_library()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -362,11 +349,19 @@ impl<MH: IMusicHoard> IAppInteract for App<MH> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn quit(&mut self) {
|
fn quit(&mut self) {
|
||||||
|
if !self.state.is_error() {
|
||||||
|
self.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_quit(&mut self) {
|
||||||
self.running = false;
|
self.running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self) -> Result<(), AppError> {
|
fn save(&mut self) {
|
||||||
Ok(self.music_hoard.save_to_database()?)
|
if let Err(err) = self.music_hoard.save_to_database() {
|
||||||
|
self.state = AppState::Error(err.to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&mut self) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES> {
|
fn state(&mut self) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES> {
|
||||||
@ -642,14 +637,50 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn running() {
|
fn running_quit() {
|
||||||
let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap();
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
app.quit();
|
app.quit();
|
||||||
assert!(!app.is_running());
|
assert!(!app.is_running());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_quit() {
|
||||||
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
app.state = AppState::Error(String::from("get rekt"));
|
||||||
|
|
||||||
|
app.quit();
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
app.dismiss_error();
|
||||||
|
|
||||||
|
app.quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn running_force_quit() {
|
||||||
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
app.force_quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn error_force_quit() {
|
||||||
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
|
assert!(app.is_running());
|
||||||
|
|
||||||
|
app.state = AppState::Error(String::from("get rekt"));
|
||||||
|
|
||||||
|
app.force_quit();
|
||||||
|
assert!(!app.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn save() {
|
fn save() {
|
||||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
@ -659,15 +690,47 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Ok(()));
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
let mut app = App::new(music_hoard).unwrap();
|
let mut app = App::new(music_hoard);
|
||||||
|
|
||||||
let result = app.save();
|
app.save();
|
||||||
assert!(result.is_ok());
|
assert!(app.state.is_browse());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn save_error() {
|
||||||
|
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_save_to_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
|
||||||
|
let mut app = App::new(music_hoard);
|
||||||
|
|
||||||
|
app.save();
|
||||||
|
|
||||||
|
assert!(app.state.is_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn init_error() {
|
||||||
|
let mut music_hoard = MockIMusicHoard::new();
|
||||||
|
|
||||||
|
music_hoard
|
||||||
|
.expect_load_from_database()
|
||||||
|
.times(1)
|
||||||
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
music_hoard.expect_get_collection().return_const(vec![]);
|
||||||
|
|
||||||
|
let app = App::new(music_hoard);
|
||||||
|
|
||||||
|
assert!(app.is_running());
|
||||||
|
assert!(app.state.is_error());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn modifiers() {
|
fn modifiers() {
|
||||||
let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap();
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert_eq!(app.selection.active, Category::Artist);
|
assert_eq!(app.selection.active, Category::Artist);
|
||||||
@ -759,7 +822,7 @@ mod tests {
|
|||||||
let mut collection = COLLECTION.to_owned();
|
let mut collection = COLLECTION.to_owned();
|
||||||
collection[0].albums[0].tracks = vec![];
|
collection[0].albums[0].tracks = vec![];
|
||||||
|
|
||||||
let mut app = App::new(music_hoard(collection)).unwrap();
|
let mut app = App::new(music_hoard(collection));
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert_eq!(app.selection.active, Category::Artist);
|
assert_eq!(app.selection.active, Category::Artist);
|
||||||
@ -788,7 +851,7 @@ mod tests {
|
|||||||
let mut collection = COLLECTION.to_owned();
|
let mut collection = COLLECTION.to_owned();
|
||||||
collection[0].albums = vec![];
|
collection[0].albums = vec![];
|
||||||
|
|
||||||
let mut app = App::new(music_hoard(collection)).unwrap();
|
let mut app = App::new(music_hoard(collection));
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert_eq!(app.selection.active, Category::Artist);
|
assert_eq!(app.selection.active, Category::Artist);
|
||||||
@ -827,7 +890,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn no_artists() {
|
fn no_artists() {
|
||||||
let mut app = App::new(music_hoard(vec![])).unwrap();
|
let mut app = App::new(music_hoard(vec![]));
|
||||||
assert!(app.is_running());
|
assert!(app.is_running());
|
||||||
|
|
||||||
assert_eq!(app.selection.active, Category::Artist);
|
assert_eq!(app.selection.active, Category::Artist);
|
||||||
@ -880,7 +943,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn info_overlay() {
|
fn info_overlay() {
|
||||||
let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap();
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
assert!(app.state().is_browse());
|
assert!(app.state().is_browse());
|
||||||
|
|
||||||
app.show_info_overlay();
|
app.show_info_overlay();
|
||||||
@ -892,7 +955,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reload_go_back() {
|
fn reload_go_back() {
|
||||||
let mut app = App::new(music_hoard(COLLECTION.to_owned())).unwrap();
|
let mut app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||||
assert!(app.state().is_browse());
|
assert!(app.state().is_browse());
|
||||||
|
|
||||||
app.show_reload_menu();
|
app.show_reload_menu();
|
||||||
@ -911,7 +974,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Ok(()));
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
let mut app = App::new(music_hoard).unwrap();
|
let mut app = App::new(music_hoard);
|
||||||
assert!(app.state().is_browse());
|
assert!(app.state().is_browse());
|
||||||
|
|
||||||
app.show_reload_menu();
|
app.show_reload_menu();
|
||||||
@ -930,7 +993,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Ok(()));
|
.return_once(|| Ok(()));
|
||||||
|
|
||||||
let mut app = App::new(music_hoard).unwrap();
|
let mut app = App::new(music_hoard);
|
||||||
assert!(app.state().is_browse());
|
assert!(app.state().is_browse());
|
||||||
|
|
||||||
app.show_reload_menu();
|
app.show_reload_menu();
|
||||||
@ -949,7 +1012,7 @@ mod tests {
|
|||||||
.times(1)
|
.times(1)
|
||||||
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||||
|
|
||||||
let mut app = App::new(music_hoard).unwrap();
|
let mut app = App::new(music_hoard);
|
||||||
assert!(app.state().is_browse());
|
assert!(app.state().is_browse());
|
||||||
|
|
||||||
app.show_reload_menu();
|
app.show_reload_menu();
|
||||||
@ -961,13 +1024,4 @@ mod tests {
|
|||||||
app.dismiss_error();
|
app.dismiss_error();
|
||||||
assert!(app.state().is_browse());
|
assert!(app.state().is_browse());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn errors() {
|
|
||||||
let app_err: AppError = musichoard::Error::DatabaseError(String::from("get rekt")).into();
|
|
||||||
|
|
||||||
assert!(!app_err.to_string().is_empty());
|
|
||||||
|
|
||||||
assert!(!format!("{:?}", app_err).is_empty());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,11 @@ use crossterm::event::{KeyEvent, MouseEvent};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
|
||||||
use crate::tui::app::AppError;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EventError {
|
pub enum EventError {
|
||||||
Send(Event),
|
Send(Event),
|
||||||
Recv,
|
Recv,
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
App(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for EventError {
|
impl fmt::Display for EventError {
|
||||||
@ -20,12 +17,6 @@ impl fmt::Display for EventError {
|
|||||||
Self::Io(ref e) => {
|
Self::Io(ref e) => {
|
||||||
write!(f, "an I/O error was triggered during event handling: {e}")
|
write!(f, "an I/O error was triggered during event handling: {e}")
|
||||||
}
|
}
|
||||||
Self::App(ref s) => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"the application returned an error during event handling: {s}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,12 +33,6 @@ impl From<mpsc::RecvError> for EventError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<AppError> for EventError {
|
|
||||||
fn from(err: AppError) -> EventError {
|
|
||||||
EventError::App(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Key(KeyEvent),
|
Key(KeyEvent),
|
||||||
@ -148,16 +133,13 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
let recv_err = EventError::Recv;
|
let recv_err = EventError::Recv;
|
||||||
let io_err = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
|
let io_err = EventError::Io(io::Error::new(io::ErrorKind::Interrupted, "interrupted"));
|
||||||
let app_err: EventError = AppError::Lib(String::from("lib error")).into();
|
|
||||||
|
|
||||||
assert!(!send_err.to_string().is_empty());
|
assert!(!send_err.to_string().is_empty());
|
||||||
assert!(!recv_err.to_string().is_empty());
|
assert!(!recv_err.to_string().is_empty());
|
||||||
assert!(!io_err.to_string().is_empty());
|
assert!(!io_err.to_string().is_empty());
|
||||||
assert!(!app_err.to_string().is_empty());
|
|
||||||
|
|
||||||
assert!(!format!("{:?}", send_err).is_empty());
|
assert!(!format!("{:?}", send_err).is_empty());
|
||||||
assert!(!format!("{:?}", recv_err).is_empty());
|
assert!(!format!("{:?}", recv_err).is_empty());
|
||||||
assert!(!format!("{:?}", io_err).is_empty());
|
assert!(!format!("{:?}", io_err).is_empty());
|
||||||
assert!(!format!("{:?}", app_err).is_empty());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// FIXME: Can code here be less verbose
|
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -18,24 +17,11 @@ pub trait IEventHandler<APP: IAppInteract> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
trait IEventHandlerPrivate<APP: IAppInteract> {
|
trait IEventHandlerPrivate<APP: IAppInteract> {
|
||||||
fn handle_key_event(app: &mut APP, key_event: KeyEvent) -> Result<(), EventError>;
|
fn handle_key_event(app: &mut APP, key_event: KeyEvent);
|
||||||
fn handle_browse_key_event(
|
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent);
|
||||||
app: &mut <APP as IAppInteract>::BS,
|
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent);
|
||||||
key_event: KeyEvent,
|
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent);
|
||||||
) -> Result<(), EventError>;
|
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, key_event: KeyEvent);
|
||||||
fn handle_info_key_event(
|
|
||||||
app: &mut <APP as IAppInteract>::IS,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError>;
|
|
||||||
fn handle_reload_key_event(
|
|
||||||
app: &mut <APP as IAppInteract>::RS,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError>;
|
|
||||||
fn handle_error_key_event(
|
|
||||||
app: &mut <APP as IAppInteract>::ES,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError>;
|
|
||||||
fn quit(app: &mut APP) -> Result<(), EventError>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
@ -52,7 +38,7 @@ impl EventHandler {
|
|||||||
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
||||||
fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError> {
|
fn handle_next_event(&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(_, _) => {}
|
||||||
};
|
};
|
||||||
@ -61,44 +47,37 @@ impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||||
fn handle_key_event(app: &mut APP, key_event: KeyEvent) -> Result<(), EventError> {
|
fn handle_key_event(app: &mut APP, key_event: KeyEvent) {
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Exit application on `ESC` or `q`.
|
// Exit application on `ESC` or `q`.
|
||||||
KeyCode::Esc | KeyCode::Char('q') => {
|
KeyCode::Esc | KeyCode::Char('q') => {
|
||||||
Self::quit(app)?;
|
app.save();
|
||||||
|
app.quit();
|
||||||
}
|
}
|
||||||
// Exit application on `Ctrl-C`.
|
// Exit application on `Ctrl-C`.
|
||||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||||
Self::quit(app)?;
|
app.force_quit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => match app.state() {
|
_ => match app.state() {
|
||||||
AppState::Browse(browse) => {
|
AppState::Browse(browse) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(
|
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event);
|
||||||
browse, key_event,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
AppState::Info(info) => {
|
AppState::Info(info) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event)?;
|
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event);
|
||||||
}
|
}
|
||||||
AppState::Reload(reload) => {
|
AppState::Reload(reload) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(
|
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event);
|
||||||
reload, key_event,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
AppState::Error(error) => {
|
AppState::Error(error) => {
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event)?;
|
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_browse_key_event(
|
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent) {
|
||||||
app: &mut <APP as IAppInteract>::BS,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError> {
|
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Category change.
|
// Category change.
|
||||||
KeyCode::Left => app.decrement_category(),
|
KeyCode::Left => app.decrement_category(),
|
||||||
@ -113,28 +92,18 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
// Othey keys.
|
// Othey keys.
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_info_key_event(
|
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent) {
|
||||||
app: &mut <APP as IAppInteract>::IS,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError> {
|
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Toggle overlay.
|
// Toggle overlay.
|
||||||
KeyCode::Char('m') | KeyCode::Char('M') => app.hide_info_overlay(),
|
KeyCode::Char('m') | KeyCode::Char('M') => app.hide_info_overlay(),
|
||||||
// Othey keys.
|
// Othey keys.
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_reload_key_event(
|
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent) {
|
||||||
app: &mut <APP as IAppInteract>::RS,
|
|
||||||
key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError> {
|
|
||||||
match key_event.code {
|
match key_event.code {
|
||||||
// Reload keys.
|
// Reload keys.
|
||||||
KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(),
|
KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(),
|
||||||
@ -144,23 +113,11 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
|||||||
// Othey keys.
|
// Othey keys.
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_error_key_event(
|
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, _key_event: KeyEvent) {
|
||||||
app: &mut <APP as IAppInteract>::ES,
|
|
||||||
_key_event: KeyEvent,
|
|
||||||
) -> Result<(), EventError> {
|
|
||||||
// Any key dismisses the error.
|
// Any key dismisses the error.
|
||||||
app.dismiss_error();
|
app.dismiss_error();
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn quit(app: &mut APP) -> Result<(), EventError> {
|
|
||||||
app.quit();
|
|
||||||
app.save()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// GRCOV_EXCL_STOP
|
// GRCOV_EXCL_STOP
|
||||||
|
@ -28,18 +28,11 @@ use crate::tui::{
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Lib(String),
|
|
||||||
Io(String),
|
Io(String),
|
||||||
Event(String),
|
Event(String),
|
||||||
ListenerPanic,
|
ListenerPanic,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<musichoard::Error> for Error {
|
|
||||||
fn from(err: musichoard::Error) -> Error {
|
|
||||||
Error::Lib(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<io::Error> for Error {
|
impl From<io::Error> for Error {
|
||||||
fn from(err: io::Error) -> Error {
|
fn from(err: io::Error) -> Error {
|
||||||
Error::Io(err.to_string())
|
Error::Io(err.to_string())
|
||||||
@ -81,9 +74,6 @@ impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
|
|||||||
handler: impl IEventHandler<APP>,
|
handler: impl IEventHandler<APP>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
while app.is_running() {
|
while app.is_running() {
|
||||||
// FIXME: rendering logic should be in render.rs - reverse Renderer/Ui relationship so
|
|
||||||
// that TAPP calls are renderer.render(ui, frame); Then rename ui -> app and render -> ui
|
|
||||||
// as it used to be originally.
|
|
||||||
self.terminal.draw(|frame| UI::render(&mut app, frame))?;
|
self.terminal.draw(|frame| UI::render(&mut app, frame))?;
|
||||||
handler.handle_next_event(&mut app)?;
|
handler.handle_next_event(&mut app)?;
|
||||||
}
|
}
|
||||||
@ -123,7 +113,7 @@ impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
|
|||||||
Ok(err) => return Err(err.into()),
|
Ok(err) => return Err(err.into()),
|
||||||
// Calling std::panic::resume_unwind(err) as recommended by the Rust docs
|
// 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
|
// 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 TAPP.
|
// the location of the panic which at the time is hidden by the TUI.
|
||||||
Err(_) => return Err(Error::ListenerPanic),
|
Err(_) => return Err(Error::ListenerPanic),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,7 +201,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn app(collection: Collection) -> App<MockIMusicHoard> {
|
fn app(collection: Collection) -> App<MockIMusicHoard> {
|
||||||
App::new(music_hoard(collection)).unwrap()
|
App::new(music_hoard(collection))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn listener() -> MockIEventListener {
|
fn listener() -> MockIEventListener {
|
||||||
@ -319,12 +309,10 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn errors() {
|
fn errors() {
|
||||||
let lib_err: Error = musichoard::Error::DatabaseError(String::from("")).into();
|
|
||||||
let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into();
|
let io_err: Error = io::Error::new(io::ErrorKind::Interrupted, "error").into();
|
||||||
let event_err: Error = EventError::Recv.into();
|
let event_err: Error = EventError::Recv.into();
|
||||||
let listener_err = Error::ListenerPanic;
|
let listener_err = Error::ListenerPanic;
|
||||||
|
|
||||||
assert!(!format!("{:?}", lib_err).is_empty());
|
|
||||||
assert!(!format!("{:?}", io_err).is_empty());
|
assert!(!format!("{:?}", io_err).is_empty());
|
||||||
assert!(!format!("{:?}", event_err).is_empty());
|
assert!(!format!("{:?}", event_err).is_empty());
|
||||||
assert!(!format!("{:?}", listener_err).is_empty());
|
assert!(!format!("{:?}", listener_err).is_empty());
|
||||||
|
Loading…
Reference in New Issue
Block a user