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
Showing only changes of commit 8ca6c4d36b - Show all commits

View File

@ -18,28 +18,26 @@ struct AppInner<MH: IMusicHoard> {
} }
pub type App<MH> = AppState< pub type App<MH> = AppState<
AppBrowseState<MH>, AppMachine<MH, AppBrowse>,
AppInfoState<MH>, AppMachine<MH, AppInfo>,
AppReloadState<MH>, AppMachine<MH, AppReload>,
AppSearchState<MH>, AppMachine<MH, AppSearch>,
AppErrorState<MH>, AppMachine<MH, AppError>,
AppCriticalState<MH>, AppMachine<MH, AppCritical>,
>; >;
pub struct AppBrowseState<MH: IMusicHoard> { pub struct AppMachine<MH: IMusicHoard, STATE> {
inner: AppInner<MH>, inner: AppInner<MH>,
state: STATE,
} }
pub struct AppInfoState<MH: IMusicHoard> { pub struct AppBrowse;
inner: AppInner<MH>,
}
pub struct AppReloadState<MH: IMusicHoard> { pub struct AppInfo;
inner: AppInner<MH>,
}
pub struct AppSearchState<MH: IMusicHoard> { pub struct AppReload;
inner: AppInner<MH>,
pub struct AppSearch {
string: String, string: String,
orig: ListSelection, orig: ListSelection,
memo: Vec<AppSearchMemo>, memo: Vec<AppSearchMemo>,
@ -50,13 +48,11 @@ struct AppSearchMemo {
char: bool, char: bool,
} }
pub struct AppErrorState<MH: IMusicHoard> { pub struct AppError {
inner: AppInner<MH>,
string: String, string: String,
} }
pub struct AppCriticalState<MH: IMusicHoard> { pub struct AppCritical {
inner: AppInner<MH>,
string: String, string: String,
} }
@ -70,10 +66,15 @@ impl<MH: IMusicHoard> App<MH> {
selection, selection,
}; };
match init_result { match init_result {
Ok(()) => Self::Browse(AppBrowseState { inner }), Ok(()) => Self::Browse(AppMachine {
Err(err) => Self::Critical(AppCriticalState {
inner, inner,
string: err.to_string(), state: AppBrowse,
}),
Err(err) => Self::Critical(AppMachine {
inner,
state: AppCritical {
string: err.to_string(),
},
}), }),
} }
} }
@ -108,12 +109,12 @@ impl<MH: IMusicHoard> App<MH> {
} }
impl<MH: IMusicHoard> IAppInteract for App<MH> { impl<MH: IMusicHoard> IAppInteract for App<MH> {
type BS = AppBrowseState<MH>; type BS = AppMachine<MH, AppBrowse>;
type IS = AppInfoState<MH>; type IS = AppMachine<MH, AppInfo>;
type RS = AppReloadState<MH>; type RS = AppMachine<MH, AppReload>;
type SS = AppSearchState<MH>; type SS = AppMachine<MH, AppSearch>;
type ES = AppErrorState<MH>; type ES = AppMachine<MH, AppError>;
type CS = AppCriticalState<MH>; type CS = AppMachine<MH, AppCritical>;
fn is_running(&self) -> bool { fn is_running(&self) -> bool {
self.inner_ref().running self.inner_ref().running
@ -129,7 +130,7 @@ impl<MH: IMusicHoard> IAppInteract for App<MH> {
} }
} }
impl<MH: IMusicHoard> IAppInteractBrowse for AppBrowseState<MH> { impl<MH: IMusicHoard> IAppInteractBrowse for AppMachine<MH, AppBrowse> {
type APP = App<MH>; type APP = App<MH>;
fn save_and_quit(mut self) -> Self::APP { fn save_and_quit(mut self) -> Self::APP {
@ -138,9 +139,11 @@ impl<MH: IMusicHoard> IAppInteractBrowse for AppBrowseState<MH> {
self.inner.running = false; self.inner.running = false;
AppState::Browse(self) AppState::Browse(self)
} }
Err(err) => AppState::Error(AppErrorState { Err(err) => AppState::Error(AppMachine {
inner: self.inner, inner: self.inner,
string: err.to_string(), state: AppError {
string: err.to_string(),
},
}), }),
} }
} }
@ -170,11 +173,17 @@ impl<MH: IMusicHoard> IAppInteractBrowse for AppBrowseState<MH> {
} }
fn show_info_overlay(self) -> Self::APP { fn show_info_overlay(self) -> Self::APP {
AppState::Info(AppInfoState { inner: self.inner }) AppState::Info(AppMachine {
inner: self.inner,
state: AppInfo,
})
} }
fn show_reload_menu(self) -> Self::APP { fn show_reload_menu(self) -> Self::APP {
AppState::Reload(AppReloadState { inner: self.inner }) AppState::Reload(AppMachine {
inner: self.inner,
state: AppReload,
})
} }
fn begin_search(mut self) -> Self::APP { fn begin_search(mut self) -> Self::APP {
@ -182,11 +191,13 @@ impl<MH: IMusicHoard> IAppInteractBrowse for AppBrowseState<MH> {
self.inner self.inner
.selection .selection
.reset_artist(self.inner.music_hoard.get_collection()); .reset_artist(self.inner.music_hoard.get_collection());
AppState::Search(AppSearchState { AppState::Search(AppMachine {
inner: self.inner, inner: self.inner,
string: String::new(), state: AppSearch {
orig, string: String::new(),
memo: vec![], orig,
memo: vec![],
},
}) })
} }
@ -195,11 +206,14 @@ impl<MH: IMusicHoard> IAppInteractBrowse for AppBrowseState<MH> {
} }
} }
impl<MH: IMusicHoard> IAppInteractInfo for AppInfoState<MH> { impl<MH: IMusicHoard> IAppInteractInfo for AppMachine<MH, AppInfo> {
type APP = App<MH>; type APP = App<MH>;
fn hide_info_overlay(self) -> Self::APP { fn hide_info_overlay(self) -> Self::APP {
AppState::Browse(AppBrowseState { inner: self.inner }) AppState::Browse(AppMachine {
inner: self.inner,
state: AppBrowse,
})
} }
fn no_op(self) -> Self::APP { fn no_op(self) -> Self::APP {
@ -207,7 +221,7 @@ impl<MH: IMusicHoard> IAppInteractInfo for AppInfoState<MH> {
} }
} }
impl<MH: IMusicHoard> IAppInteractReload for AppReloadState<MH> { impl<MH: IMusicHoard> IAppInteractReload for AppMachine<MH, AppReload> {
type APP = App<MH>; type APP = App<MH>;
fn reload_library(mut self) -> Self::APP { fn reload_library(mut self) -> Self::APP {
@ -229,7 +243,10 @@ impl<MH: IMusicHoard> IAppInteractReload for AppReloadState<MH> {
} }
fn hide_reload_menu(self) -> Self::APP { fn hide_reload_menu(self) -> Self::APP {
AppState::Browse(AppBrowseState { inner: self.inner }) AppState::Browse(AppMachine {
inner: self.inner,
state: AppBrowse,
})
} }
fn no_op(self) -> Self::APP { fn no_op(self) -> Self::APP {
@ -241,54 +258,60 @@ trait IAppInteractReloadPrivate<MH: IMusicHoard> {
fn refresh(self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH>; fn refresh(self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH>;
} }
impl<MH: IMusicHoard> IAppInteractReloadPrivate<MH> for AppReloadState<MH> { impl<MH: IMusicHoard> IAppInteractReloadPrivate<MH> for AppMachine<MH, AppReload> {
fn refresh(mut self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH> { fn refresh(mut self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH> {
match result { match result {
Ok(()) => { Ok(()) => {
self.inner self.inner
.selection .selection
.select_by_id(self.inner.music_hoard.get_collection(), previous); .select_by_id(self.inner.music_hoard.get_collection(), previous);
AppState::Browse(AppBrowseState { inner: self.inner }) AppState::Browse(AppMachine {
inner: self.inner,
state: AppBrowse,
})
} }
Err(err) => AppState::Error(AppErrorState { Err(err) => AppState::Error(AppMachine {
inner: self.inner, inner: self.inner,
string: err.to_string(), state: AppError {
string: err.to_string(),
},
}), }),
} }
} }
} }
impl<MH: IMusicHoard> IAppInteractSearch for AppSearchState<MH> { impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
type APP = App<MH>; type APP = App<MH>;
fn append_character(mut self, ch: char) -> Self::APP { fn append_character(mut self, ch: char) -> Self::APP {
let collection = self.inner.music_hoard.get_collection(); let collection = self.inner.music_hoard.get_collection();
self.string.push(ch); self.state.string.push(ch);
let index = self let index =
.inner self.inner
.selection .selection
.incremental_artist_search(collection, &self.string, false); .incremental_artist_search(collection, &self.state.string, false);
self.memo.push(AppSearchMemo { index, char: true }); self.state.memo.push(AppSearchMemo { index, char: true });
AppState::Search(self) AppState::Search(self)
} }
fn search_next(mut self) -> Self::APP { fn search_next(mut self) -> Self::APP {
let collection = self.inner.music_hoard.get_collection(); let collection = self.inner.music_hoard.get_collection();
if !self.string.is_empty() { if !self.state.string.is_empty() {
let index = let index = self.inner.selection.incremental_artist_search(
self.inner collection,
.selection &self.state.string,
.incremental_artist_search(collection, &self.string, true); true,
self.memo.push(AppSearchMemo { index, char: false }); );
self.state.memo.push(AppSearchMemo { index, char: false });
} }
AppState::Search(self) AppState::Search(self)
} }
fn step_back(mut self) -> Self::APP { fn step_back(mut self) -> Self::APP {
let collection = self.inner.music_hoard.get_collection(); let collection = self.inner.music_hoard.get_collection();
if let Some(memo) = self.memo.pop() { if let Some(memo) = self.state.memo.pop() {
if memo.char { if memo.char {
self.string.pop(); self.state.string.pop();
} }
self.inner.selection.select_artist(collection, memo.index); self.inner.selection.select_artist(collection, memo.index);
} }
@ -296,12 +319,18 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppSearchState<MH> {
} }
fn finish_search(self) -> Self::APP { fn finish_search(self) -> Self::APP {
AppState::Browse(AppBrowseState { inner: self.inner }) AppState::Browse(AppMachine {
inner: self.inner,
state: AppBrowse,
})
} }
fn cancel_search(mut self) -> Self::APP { fn cancel_search(mut self) -> Self::APP {
self.inner.selection.select_by_list(self.orig); self.inner.selection.select_by_list(self.state.orig);
AppState::Browse(AppBrowseState { inner: self.inner }) AppState::Browse(AppMachine {
inner: self.inner,
state: AppBrowse,
})
} }
fn no_op(self) -> Self::APP { fn no_op(self) -> Self::APP {
@ -309,15 +338,18 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppSearchState<MH> {
} }
} }
impl<MH: IMusicHoard> IAppInteractError for AppErrorState<MH> { impl<MH: IMusicHoard> IAppInteractError for AppMachine<MH, AppError> {
type APP = App<MH>; type APP = App<MH>;
fn dismiss_error(self) -> Self::APP { fn dismiss_error(self) -> Self::APP {
AppState::Browse(AppBrowseState { inner: self.inner }) AppState::Browse(AppMachine {
inner: self.inner,
state: AppBrowse,
})
} }
} }
impl<MH: IMusicHoard> IAppInteractCritical for AppCriticalState<MH> { impl<MH: IMusicHoard> IAppInteractCritical for AppMachine<MH, AppCritical> {
type APP = App<MH>; type APP = App<MH>;
fn no_op(self) -> Self::APP { fn no_op(self) -> Self::APP {
@ -342,15 +374,15 @@ impl<MH: IMusicHoard> IAppAccess for App<MH> {
}, },
AppState::Search(search) => AppPublic { AppState::Search(search) => AppPublic {
inner: (&mut search.inner).into(), inner: (&mut search.inner).into(),
state: AppState::Search(&search.string), state: AppState::Search(&search.state.string),
}, },
AppState::Error(error) => AppPublic { AppState::Error(error) => AppPublic {
inner: (&mut error.inner).into(), inner: (&mut error.inner).into(),
state: AppState::Error(&error.string), state: AppState::Error(&error.state.string),
}, },
AppState::Critical(critical) => AppPublic { AppState::Critical(critical) => AppPublic {
inner: (&mut critical.inner).into(), inner: (&mut critical.inner).into(),
state: AppState::Error(&critical.string), state: AppState::Error(&critical.state.string),
}, },
} }
} }
@ -473,9 +505,11 @@ mod tests {
let app = App::new(music_hoard); let app = App::new(music_hoard);
assert!(app.is_running()); assert!(app.is_running());
let app = App::Error(AppErrorState { let app = App::Error(AppMachine {
inner: app.unwrap_browse().inner, inner: app.unwrap_browse().inner,
string: String::from("get rekt"), state: AppError {
string: String::from("get rekt"),
},
}); });
let error = app.unwrap_error(); let error = app.unwrap_error();
@ -500,9 +534,11 @@ mod tests {
let app = App::new(music_hoard(COLLECTION.to_owned())); let app = App::new(music_hoard(COLLECTION.to_owned()));
assert!(app.is_running()); assert!(app.is_running());
let app = App::Error(AppErrorState { let app = App::Error(AppMachine {
inner: app.unwrap_browse().inner, inner: app.unwrap_browse().inner,
string: String::from("get rekt"), state: AppError {
string: String::from("get rekt"),
},
}); });
let app = app.force_quit(); let app = app.force_quit();