State machine solution works
This commit is contained in:
parent
5979e75dcd
commit
c2920aac81
1340
src/tui/app/app.rs
1340
src/tui/app/app.rs
File diff suppressed because it is too large
Load Diff
@ -14,19 +14,21 @@ use crate::tui::{
|
||||
event::{Event, EventError, EventReceiver},
|
||||
};
|
||||
|
||||
use super::app::app::IAppInteractCritical;
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
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> {
|
||||
fn handle_key_event(app: &mut APP, key_event: KeyEvent);
|
||||
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent);
|
||||
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent);
|
||||
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent);
|
||||
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent);
|
||||
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, key_event: KeyEvent);
|
||||
fn handle_critical_key_event(app: &mut <APP as IAppInteract>::CS, key_event: KeyEvent);
|
||||
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP;
|
||||
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP;
|
||||
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP;
|
||||
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP;
|
||||
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP;
|
||||
fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
|
||||
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
|
||||
}
|
||||
|
||||
pub struct EventHandler {
|
||||
@ -41,58 +43,52 @@ impl 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()? {
|
||||
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::Resize(_, _) => {}
|
||||
};
|
||||
Ok(())
|
||||
Ok(app)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
match key_event.code {
|
||||
// Exit application on `Ctrl-C`.
|
||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||
app.force_quit();
|
||||
return;
|
||||
}
|
||||
KeyCode::Char('c') | KeyCode::Char('C') => return app.force_quit(),
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
match app.state() {
|
||||
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) => {
|
||||
<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) => {
|
||||
<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) => {
|
||||
<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) => {
|
||||
<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) => {
|
||||
<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 {
|
||||
// Exit application on `ESC` or `q`.
|
||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => {
|
||||
app.save();
|
||||
app.quit();
|
||||
}
|
||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.save_and_quit(),
|
||||
// Category change.
|
||||
KeyCode::Left => app.decrement_category(),
|
||||
KeyCode::Right => app.increment_category(),
|
||||
@ -108,15 +104,17 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||
// Toggle search.
|
||||
KeyCode::Char('s') | KeyCode::Char('S') => {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
app.begin_search();
|
||||
app.begin_search()
|
||||
} else {
|
||||
app.no_op()
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
// Toggle overlay.
|
||||
KeyCode::Esc
|
||||
@ -125,11 +123,11 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||
| KeyCode::Char('m')
|
||||
| KeyCode::Char('M') => app.hide_info_overlay(),
|
||||
// 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 {
|
||||
// Reload keys.
|
||||
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') => app.hide_reload_menu(),
|
||||
// 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 {
|
||||
match key_event.code {
|
||||
KeyCode::Char('s') | KeyCode::Char('S') => {
|
||||
app.search_next();
|
||||
}
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => {
|
||||
app.cancel_search();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
return;
|
||||
return match key_event.code {
|
||||
KeyCode::Char('s') | KeyCode::Char('S') => app.search_next(),
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => app.cancel_search(),
|
||||
_ => app.no_op(),
|
||||
};
|
||||
}
|
||||
|
||||
match key_event.code {
|
||||
@ -166,17 +159,18 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||
// Return.
|
||||
KeyCode::Esc | KeyCode::Enter => app.finish_search(),
|
||||
// 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.
|
||||
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.
|
||||
app.no_op()
|
||||
}
|
||||
}
|
||||
// GRCOV_EXCL_STOP
|
||||
|
@ -75,7 +75,7 @@ impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
|
||||
) -> Result<(), Error> {
|
||||
while app.is_running() {
|
||||
self.terminal.draw(|frame| UI::render(&mut app, frame))?;
|
||||
handler.handle_next_event(&mut app)?;
|
||||
app = handler.handle_next_event(app)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -219,10 +219,7 @@ mod tests {
|
||||
let mut handler = MockIEventHandler::new();
|
||||
handler
|
||||
.expect_handle_next_event()
|
||||
.return_once(|app: &mut App<MockIMusicHoard>| {
|
||||
app.force_quit();
|
||||
Ok(())
|
||||
});
|
||||
.return_once(|app: App<MockIMusicHoard>| Ok(app.force_quit()));
|
||||
handler
|
||||
}
|
||||
|
||||
|
@ -681,12 +681,12 @@ impl IUi for Ui {
|
||||
let selection = app.selection;
|
||||
let state = app.state;
|
||||
|
||||
Self::render_main_frame(collection, selection, state, frame);
|
||||
Self::render_main_frame(collection, selection, &state, frame);
|
||||
match state {
|
||||
AppState::Info(_) => Self::render_info_overlay(collection, selection, frame),
|
||||
AppState::Reload(_) => Self::render_reload_overlay(frame),
|
||||
AppState::Error(ref msg) => Self::render_error_overlay("Error", msg, frame),
|
||||
AppState::Critical(ref msg) => Self::render_error_overlay("Critical Error", msg, frame),
|
||||
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
|
||||
AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -719,26 +719,23 @@ mod tests {
|
||||
let mut app = AppPublic {
|
||||
collection,
|
||||
selection,
|
||||
state: &AppState::Browse(()),
|
||||
state: AppState::Browse(()),
|
||||
};
|
||||
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();
|
||||
|
||||
app.state = &AppState::Reload(());
|
||||
app.state = AppState::Reload(());
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
|
||||
let binding = AppState::Search(String::new());
|
||||
app.state = &binding;
|
||||
app.state = AppState::Search("");
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
|
||||
let binding = AppState::Error(String::from("get rekt scrub"));
|
||||
app.state = &binding;
|
||||
app.state = AppState::Error("get rekt scrub");
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
|
||||
let binding = AppState::Critical(String::from("get critically rekt scrub"));
|
||||
app.state = &binding;
|
||||
app.state = AppState::Critical("get critically rekt scrub");
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user