State machine solution works
Some checks failed
Cargo CI / Build and Test (pull_request) Failing after 1m42s
Cargo CI / Lint (pull_request) Successful in 1m14s

This commit is contained in:
Wojciech Kozlowski 2024-02-16 19:20:46 +01:00
parent 5979e75dcd
commit c2920aac81
4 changed files with 815 additions and 645 deletions

File diff suppressed because it is too large Load Diff

View File

@ -14,19 +14,21 @@ use crate::tui::{
event::{Event, EventError, EventReceiver}, event::{Event, EventError, EventReceiver},
}; };
use super::app::app::IAppInteractCritical;
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
pub trait IEventHandler<APP: IAppInteract> { pub trait IEventHandler<APP: IAppInteract> {
fn handle_next_event(&self, app: &mut APP) -> Result<(), EventError>; fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
} }
trait IEventHandlerPrivate<APP: IAppInteract> { trait IEventHandlerPrivate<APP: IAppInteract> {
fn handle_key_event(app: &mut APP, key_event: KeyEvent); fn handle_key_event(app: APP, key_event: KeyEvent) -> APP;
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent); fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP;
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent); fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP;
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent); fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP;
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent); fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP;
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, key_event: KeyEvent); fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
fn handle_critical_key_event(app: &mut <APP as IAppInteract>::CS, key_event: KeyEvent); fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
} }
pub struct EventHandler { pub struct EventHandler {
@ -41,58 +43,52 @@ 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, mut app: APP) -> Result<APP, EventError> {
match self.events.recv()? { match self.events.recv()? {
Event::Key(key_event) => Self::handle_key_event(app, key_event), Event::Key(key_event) => app = Self::handle_key_event(app, key_event),
Event::Mouse(_) => {} Event::Mouse(_) => {}
Event::Resize(_, _) => {} Event::Resize(_, _) => {}
}; };
Ok(()) Ok(app)
} }
} }
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler { impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
fn handle_key_event(app: &mut APP, key_event: KeyEvent) { fn handle_key_event(app: APP, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL { if key_event.modifiers == KeyModifiers::CONTROL {
match key_event.code { match key_event.code {
// Exit application on `Ctrl-C`. // Exit application on `Ctrl-C`.
KeyCode::Char('c') | KeyCode::Char('C') => { KeyCode::Char('c') | KeyCode::Char('C') => return app.force_quit(),
app.force_quit();
return;
}
_ => {} _ => {}
} };
} }
match app.state() { match app.state() {
AppState::Browse(browse) => { AppState::Browse(browse) => {
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event); <Self as IEventHandlerPrivate<APP>>::handle_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(reload, key_event); <Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event)
} }
AppState::Search(search) => { AppState::Search(search) => {
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, key_event); <Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, 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)
} }
AppState::Critical(critical) => { AppState::Critical(critical) => {
<Self as IEventHandlerPrivate<APP>>::handle_critical_key_event(critical, key_event); <Self as IEventHandlerPrivate<APP>>::handle_critical_key_event(critical, key_event)
} }
} }
} }
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent) { fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP {
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::Char('Q') => { KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.save_and_quit(),
app.save();
app.quit();
}
// Category change. // Category change.
KeyCode::Left => app.decrement_category(), KeyCode::Left => app.decrement_category(),
KeyCode::Right => app.increment_category(), KeyCode::Right => app.increment_category(),
@ -108,15 +104,17 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
// Toggle search. // Toggle search.
KeyCode::Char('s') | KeyCode::Char('S') => { KeyCode::Char('s') | KeyCode::Char('S') => {
if key_event.modifiers == KeyModifiers::CONTROL { if key_event.modifiers == KeyModifiers::CONTROL {
app.begin_search(); app.begin_search()
} else {
app.no_op()
} }
} }
// Othey keys. // Othey keys.
_ => {} _ => app.no_op(),
} }
} }
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent) { fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP {
match key_event.code { match key_event.code {
// Toggle overlay. // Toggle overlay.
KeyCode::Esc KeyCode::Esc
@ -125,11 +123,11 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
| KeyCode::Char('m') | KeyCode::Char('m')
| KeyCode::Char('M') => app.hide_info_overlay(), | KeyCode::Char('M') => app.hide_info_overlay(),
// Othey keys. // Othey keys.
_ => {} _ => app.no_op(),
} }
} }
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent) { fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP {
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(),
@ -141,22 +139,17 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
| KeyCode::Char('g') | KeyCode::Char('g')
| KeyCode::Char('G') => app.hide_reload_menu(), | KeyCode::Char('G') => app.hide_reload_menu(),
// Othey keys. // Othey keys.
_ => {} _ => app.no_op(),
} }
} }
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent) { fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL { if key_event.modifiers == KeyModifiers::CONTROL {
match key_event.code { return match key_event.code {
KeyCode::Char('s') | KeyCode::Char('S') => { KeyCode::Char('s') | KeyCode::Char('S') => app.search_next(),
app.search_next(); KeyCode::Char('g') | KeyCode::Char('G') => app.cancel_search(),
} _ => app.no_op(),
KeyCode::Char('g') | KeyCode::Char('G') => { };
app.cancel_search();
}
_ => {}
}
return;
} }
match key_event.code { match key_event.code {
@ -166,17 +159,18 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
// Return. // Return.
KeyCode::Esc | KeyCode::Enter => app.finish_search(), KeyCode::Esc | KeyCode::Enter => app.finish_search(),
// Othey keys. // Othey keys.
_ => {} _ => app.no_op(),
} }
} }
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, _key_event: KeyEvent) { fn handle_error_key_event(app: <APP as IAppInteract>::ES, _key_event: KeyEvent) -> APP {
// Any key dismisses the error. // Any key dismisses the error.
app.dismiss_error(); app.dismiss_error()
} }
fn handle_critical_key_event(_app: &mut <APP as IAppInteract>::CS, _key_event: KeyEvent) { fn handle_critical_key_event(app: <APP as IAppInteract>::CS, _key_event: KeyEvent) -> APP {
// No action is allowed. // No action is allowed.
app.no_op()
} }
} }
// GRCOV_EXCL_STOP // GRCOV_EXCL_STOP

View File

@ -75,7 +75,7 @@ impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
) -> Result<(), Error> { ) -> Result<(), Error> {
while app.is_running() { while app.is_running() {
self.terminal.draw(|frame| UI::render(&mut app, frame))?; self.terminal.draw(|frame| UI::render(&mut app, frame))?;
handler.handle_next_event(&mut app)?; app = handler.handle_next_event(app)?;
} }
Ok(()) Ok(())
@ -219,10 +219,7 @@ mod tests {
let mut handler = MockIEventHandler::new(); let mut handler = MockIEventHandler::new();
handler handler
.expect_handle_next_event() .expect_handle_next_event()
.return_once(|app: &mut App<MockIMusicHoard>| { .return_once(|app: App<MockIMusicHoard>| Ok(app.force_quit()));
app.force_quit();
Ok(())
});
handler handler
} }

View File

@ -681,12 +681,12 @@ impl IUi for Ui {
let selection = app.selection; let selection = app.selection;
let state = app.state; let state = app.state;
Self::render_main_frame(collection, selection, state, frame); Self::render_main_frame(collection, selection, &state, frame);
match state { match state {
AppState::Info(_) => Self::render_info_overlay(collection, selection, frame), AppState::Info(_) => Self::render_info_overlay(collection, selection, frame),
AppState::Reload(_) => Self::render_reload_overlay(frame), AppState::Reload(_) => Self::render_reload_overlay(frame),
AppState::Error(ref msg) => Self::render_error_overlay("Error", msg, frame), AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
AppState::Critical(ref msg) => Self::render_error_overlay("Critical Error", msg, frame), AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
_ => {} _ => {}
} }
} }
@ -719,26 +719,23 @@ mod tests {
let mut app = AppPublic { let mut app = AppPublic {
collection, collection,
selection, selection,
state: &AppState::Browse(()), state: AppState::Browse(()),
}; };
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
app.state = &AppState::Info(()); app.state = AppState::Info(());
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
app.state = &AppState::Reload(()); app.state = AppState::Reload(());
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let binding = AppState::Search(String::new()); app.state = AppState::Search("");
app.state = &binding;
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let binding = AppState::Error(String::from("get rekt scrub")); app.state = AppState::Error("get rekt scrub");
app.state = &binding;
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
let binding = AppState::Critical(String::from("get critically rekt scrub")); app.state = AppState::Critical("get critically rekt scrub");
app.state = &binding;
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }