Provide search functionality through the TUI #134

Merged
wojtek merged 35 commits from 24---provide-search-functionality-through-the-tui into main 2024-02-18 22:12:42 +01:00
5 changed files with 134 additions and 128 deletions
Showing only changes of commit 001bc81b0d - Show all commits

View File

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

View File

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

View File

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

View File

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

View File

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