Extract public interface of app
This commit is contained in:
parent
e96963f37a
commit
001bc81b0d
@ -1,124 +1,16 @@
|
|||||||
// FIXME: Split file apart
|
// FIXME: Split file apart
|
||||||
#![allow(clippy::module_inception)]
|
#![allow(clippy::module_inception)]
|
||||||
|
|
||||||
use musichoard::collection::Collection;
|
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::selection::{Delta, IdSelection, ListSelection, Selection},
|
app::{
|
||||||
|
selection::{Delta, IdSelection, ListSelection, Selection},
|
||||||
|
AppPublic, AppPublicInner, AppState, IAppAccess, IAppInteract, IAppInteractBrowse,
|
||||||
|
IAppInteractCritical, IAppInteractError, IAppInteractInfo, IAppInteractReload,
|
||||||
|
IAppInteractSearch,
|
||||||
|
},
|
||||||
lib::IMusicHoard,
|
lib::IMusicHoard,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum AppState<BS, IS, RS, SS, ES, CS> {
|
|
||||||
Browse(BS),
|
|
||||||
Info(IS),
|
|
||||||
Reload(RS),
|
|
||||||
Search(SS),
|
|
||||||
Error(ES),
|
|
||||||
Critical(CS),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<BS, IS, RS, SS, ES, CS> AppState<BS, IS, RS, SS, ES, CS> {
|
|
||||||
pub fn is_search(&self) -> bool {
|
|
||||||
matches!(self, AppState::Search(_))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteract {
|
|
||||||
type BS: IAppInteractBrowse<APP = Self>;
|
|
||||||
type IS: IAppInteractInfo<APP = Self>;
|
|
||||||
type RS: IAppInteractReload<APP = Self>;
|
|
||||||
type SS: IAppInteractSearch<APP = Self>;
|
|
||||||
type ES: IAppInteractError<APP = Self>;
|
|
||||||
type CS: IAppInteractCritical<APP = Self>;
|
|
||||||
|
|
||||||
fn is_running(&self) -> bool;
|
|
||||||
fn force_quit(self) -> Self;
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
fn state(self) -> AppState<Self::BS, Self::IS, Self::RS, Self::SS, Self::ES, Self::CS>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteractBrowse {
|
|
||||||
type APP: IAppInteract;
|
|
||||||
|
|
||||||
fn save_and_quit(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn increment_category(self) -> Self::APP;
|
|
||||||
fn decrement_category(self) -> Self::APP;
|
|
||||||
fn increment_selection(self, delta: Delta) -> Self::APP;
|
|
||||||
fn decrement_selection(self, delta: Delta) -> Self::APP;
|
|
||||||
|
|
||||||
fn show_info_overlay(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn show_reload_menu(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn begin_search(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn no_op(self) -> Self::APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteractInfo {
|
|
||||||
type APP: IAppInteract;
|
|
||||||
|
|
||||||
fn hide_info_overlay(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn no_op(self) -> Self::APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteractReload {
|
|
||||||
type APP: IAppInteract;
|
|
||||||
|
|
||||||
fn reload_library(self) -> Self::APP;
|
|
||||||
fn reload_database(self) -> Self::APP;
|
|
||||||
fn hide_reload_menu(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn no_op(self) -> Self::APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteractSearch {
|
|
||||||
type APP: IAppInteract;
|
|
||||||
|
|
||||||
fn append_character(self, ch: char) -> Self::APP;
|
|
||||||
fn search_next(self) -> Self::APP;
|
|
||||||
fn step_back(self) -> Self::APP;
|
|
||||||
fn finish_search(self) -> Self::APP;
|
|
||||||
fn cancel_search(self) -> Self::APP;
|
|
||||||
|
|
||||||
fn no_op(self) -> Self::APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteractError {
|
|
||||||
type APP: IAppInteract;
|
|
||||||
|
|
||||||
fn dismiss_error(self) -> Self::APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IAppInteractCritical {
|
|
||||||
type APP: IAppInteract;
|
|
||||||
|
|
||||||
fn no_op(self) -> Self::APP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It would be preferable to have a getter for each field separately. However, the selection field
|
|
||||||
// needs to be mutably accessible requiring a mutable borrow of the entire struct if behind a trait.
|
|
||||||
// This in turn complicates simultaneous field access since only a single mutable borrow is allowed.
|
|
||||||
// Therefore, all fields are grouped into a single struct and returned as a batch.
|
|
||||||
pub trait IAppAccess {
|
|
||||||
fn get(&mut self) -> AppPublic;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type AppPublicState<'app> = AppState<(), (), (), &'app str, &'app str, &'app str>;
|
|
||||||
|
|
||||||
pub struct AppPublic<'app> {
|
|
||||||
pub inner: AppPublicInner<'app>,
|
|
||||||
pub state: AppPublicState<'app>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AppPublicInner<'app> {
|
|
||||||
pub collection: &'app Collection,
|
|
||||||
pub selection: &'app mut Selection,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AppInner<MH: IMusicHoard> {
|
struct AppInner<MH: IMusicHoard> {
|
||||||
running: bool,
|
running: bool,
|
||||||
music_hoard: MH,
|
music_hoard: MH,
|
||||||
@ -460,7 +352,13 @@ impl<'app, MH: IMusicHoard> From<&'app mut AppInner<MH>> for AppPublicInner<'app
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tui::{app::selection::Category, lib::MockIMusicHoard, testmod::COLLECTION};
|
use musichoard::collection::Collection;
|
||||||
|
|
||||||
|
use crate::tui::{
|
||||||
|
app::{selection::Category, AppPublicState},
|
||||||
|
lib::MockIMusicHoard,
|
||||||
|
testmod::COLLECTION,
|
||||||
|
};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -1,2 +1,118 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod selection;
|
pub mod selection;
|
||||||
|
|
||||||
|
use musichoard::collection::Collection;
|
||||||
|
|
||||||
|
use selection::{Delta, Selection};
|
||||||
|
|
||||||
|
pub enum AppState<BS, IS, RS, SS, ES, CS> {
|
||||||
|
Browse(BS),
|
||||||
|
Info(IS),
|
||||||
|
Reload(RS),
|
||||||
|
Search(SS),
|
||||||
|
Error(ES),
|
||||||
|
Critical(CS),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IAppInteract {
|
||||||
|
type BS: IAppInteractBrowse<APP = Self>;
|
||||||
|
type IS: IAppInteractInfo<APP = Self>;
|
||||||
|
type RS: IAppInteractReload<APP = Self>;
|
||||||
|
type SS: IAppInteractSearch<APP = Self>;
|
||||||
|
type ES: IAppInteractError<APP = Self>;
|
||||||
|
type CS: IAppInteractCritical<APP = Self>;
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool;
|
||||||
|
fn force_quit(self) -> Self;
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn state(self) -> AppState<Self::BS, Self::IS, Self::RS, Self::SS, Self::ES, Self::CS>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: move to returning specific states - for errors, return Result
|
||||||
|
pub trait IAppInteractBrowse {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn save_and_quit(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn increment_category(self) -> Self::APP;
|
||||||
|
fn decrement_category(self) -> Self::APP;
|
||||||
|
fn increment_selection(self, delta: Delta) -> Self::APP;
|
||||||
|
fn decrement_selection(self, delta: Delta) -> Self::APP;
|
||||||
|
|
||||||
|
fn show_info_overlay(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn show_reload_menu(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn begin_search(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IAppInteractInfo {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn hide_info_overlay(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IAppInteractReload {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn reload_library(self) -> Self::APP;
|
||||||
|
fn reload_database(self) -> Self::APP;
|
||||||
|
fn hide_reload_menu(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IAppInteractSearch {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn append_character(self, ch: char) -> Self::APP;
|
||||||
|
fn search_next(self) -> Self::APP;
|
||||||
|
fn step_back(self) -> Self::APP;
|
||||||
|
fn finish_search(self) -> Self::APP;
|
||||||
|
fn cancel_search(self) -> Self::APP;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IAppInteractError {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn dismiss_error(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IAppInteractCritical {
|
||||||
|
type APP: IAppInteract;
|
||||||
|
|
||||||
|
fn no_op(self) -> Self::APP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It would be preferable to have a getter for each field separately. However, the selection field
|
||||||
|
// needs to be mutably accessible requiring a mutable borrow of the entire struct if behind a trait.
|
||||||
|
// This in turn complicates simultaneous field access since only a single mutable borrow is allowed.
|
||||||
|
// Therefore, all fields are grouped into a single struct and returned as a batch.
|
||||||
|
pub trait IAppAccess {
|
||||||
|
fn get(&mut self) -> AppPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppPublic<'app> {
|
||||||
|
pub inner: AppPublicInner<'app>,
|
||||||
|
pub state: AppPublicState<'app>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppPublicInner<'app> {
|
||||||
|
pub collection: &'app Collection,
|
||||||
|
pub selection: &'app mut Selection,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AppPublicState<'app> = AppState<(), (), (), &'app str, &'app str, &'app str>;
|
||||||
|
|
||||||
|
impl<BS, IS, RS, SS, ES, CS> AppState<BS, IS, RS, SS, ES, CS> {
|
||||||
|
pub fn is_search(&self) -> bool {
|
||||||
|
matches!(self, AppState::Search(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,17 +5,12 @@ use mockall::automock;
|
|||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{
|
||||||
app::{
|
selection::Delta, AppState, IAppInteract, IAppInteractBrowse, IAppInteractCritical,
|
||||||
AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo,
|
IAppInteractError, IAppInteractInfo, IAppInteractReload, IAppInteractSearch,
|
||||||
IAppInteractReload, IAppInteractSearch,
|
|
||||||
},
|
|
||||||
selection::Delta,
|
|
||||||
},
|
},
|
||||||
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: APP) -> Result<APP, EventError>;
|
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
|
||||||
|
@ -19,7 +19,7 @@ use std::io;
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::app::{IAppAccess, IAppInteract},
|
app::{IAppAccess, IAppInteract},
|
||||||
event::EventError,
|
event::EventError,
|
||||||
handler::IEventHandler,
|
handler::IEventHandler,
|
||||||
listener::IEventListener,
|
listener::IEventListener,
|
||||||
|
@ -14,8 +14,8 @@ use ratatui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::tui::app::{
|
use crate::tui::app::{
|
||||||
app::{AppPublicState, AppState, IAppAccess},
|
|
||||||
selection::{Category, Selection, WidgetState},
|
selection::{Category, Selection, WidgetState},
|
||||||
|
AppPublicState, AppState, IAppAccess,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait IUi {
|
pub trait IUi {
|
||||||
@ -695,10 +695,7 @@ impl IUi for Ui {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tui::{
|
use crate::tui::{
|
||||||
app::{
|
app::{selection::Delta, AppPublic, AppPublicInner},
|
||||||
app::{AppPublic, AppPublicInner},
|
|
||||||
selection::Delta,
|
|
||||||
},
|
|
||||||
testmod::COLLECTION,
|
testmod::COLLECTION,
|
||||||
tests::terminal,
|
tests::terminal,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user