Extract public interface of app

This commit is contained in:
Wojciech Kozlowski 2024-02-17 22:34:37 +01:00
parent e96963f37a
commit 001bc81b0d
5 changed files with 134 additions and 128 deletions

View File

@ -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::*;

View File

@ -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(_))
}
}

View File

@ -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>;

View File

@ -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,

View File

@ -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,
}; };