Provide search functionality through the TUI #134
@ -1,124 +1,16 @@
|
||||
// FIXME: Split file apart
|
||||
#![allow(clippy::module_inception)]
|
||||
|
||||
use musichoard::collection::Collection;
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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> {
|
||||
running: bool,
|
||||
music_hoard: MH,
|
||||
@ -460,7 +352,13 @@ impl<'app, MH: IMusicHoard> From<&'app mut AppInner<MH>> for AppPublicInner<'app
|
||||
|
||||
#[cfg(test)]
|
||||
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::*;
|
||||
|
||||
|
@ -1,2 +1,118 @@
|
||||
pub mod app;
|
||||
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::{
|
||||
app::{
|
||||
app::{
|
||||
AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo,
|
||||
IAppInteractReload, IAppInteractSearch,
|
||||
},
|
||||
selection::Delta,
|
||||
selection::Delta, AppState, IAppInteract, IAppInteractBrowse, IAppInteractCritical,
|
||||
IAppInteractError, IAppInteractInfo, IAppInteractReload, IAppInteractSearch,
|
||||
},
|
||||
event::{Event, EventError, EventReceiver},
|
||||
};
|
||||
|
||||
use super::app::app::IAppInteractCritical;
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait IEventHandler<APP: IAppInteract> {
|
||||
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
|
||||
|
@ -19,7 +19,7 @@ use std::io;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::tui::{
|
||||
app::app::{IAppAccess, IAppInteract},
|
||||
app::{IAppAccess, IAppInteract},
|
||||
event::EventError,
|
||||
handler::IEventHandler,
|
||||
listener::IEventListener,
|
||||
|
@ -14,8 +14,8 @@ use ratatui::{
|
||||
};
|
||||
|
||||
use crate::tui::app::{
|
||||
app::{AppPublicState, AppState, IAppAccess},
|
||||
selection::{Category, Selection, WidgetState},
|
||||
AppPublicState, AppState, IAppAccess,
|
||||
};
|
||||
|
||||
pub trait IUi {
|
||||
@ -695,10 +695,7 @@ impl IUi for Ui {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::{AppPublic, AppPublicInner},
|
||||
selection::Delta,
|
||||
},
|
||||
app::{selection::Delta, AppPublic, AppPublicInner},
|
||||
testmod::COLLECTION,
|
||||
tests::terminal,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user