Provide search functionality through the TUI #134
1131
src/tui/app/app.rs
1131
src/tui/app/app.rs
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
pub mod app;
|
||||
pub mod selection;
|
||||
mod state;
|
||||
|
||||
use musichoard::collection::Collection;
|
||||
|
||||
|
108
src/tui/app/state/browse.rs
Normal file
108
src/tui/app/state/browse.rs
Normal file
@ -0,0 +1,108 @@
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
selection::{Delta, ListSelection},
|
||||
state::{critical::AppCritical, AppInner, AppMachine},
|
||||
AppPublic, AppState, IAppInteractBrowse,
|
||||
},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppBrowse;
|
||||
|
||||
impl<MH: IMusicHoard> AppMachine<MH, AppBrowse> {
|
||||
pub fn browse(inner: AppInner<MH>) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: AppBrowse,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(mut music_hoard: MH) -> Result<Self, AppMachine<MH, AppCritical>> {
|
||||
let init_result = Self::init(&mut music_hoard);
|
||||
let inner = AppInner::new(music_hoard);
|
||||
match init_result {
|
||||
Ok(()) => Ok(AppMachine::browse(inner)),
|
||||
Err(err) => Err(AppMachine::critical(inner, err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn init(music_hoard: &mut MH) -> Result<(), musichoard::Error> {
|
||||
music_hoard.load_from_database()?;
|
||||
music_hoard.rescan_library()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> From<AppMachine<MH, AppBrowse>> for App<MH> {
|
||||
fn from(machine: AppMachine<MH, AppBrowse>) -> Self {
|
||||
AppState::Browse(machine)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppBrowse>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<MH, AppBrowse>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Browse(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractBrowse for AppMachine<MH, AppBrowse> {
|
||||
type APP = App<MH>;
|
||||
|
||||
fn save_and_quit(mut self) -> Self::APP {
|
||||
match self.inner.music_hoard.save_to_database() {
|
||||
Ok(_) => {
|
||||
self.inner.running = false;
|
||||
self.into()
|
||||
}
|
||||
Err(err) => AppMachine::error(self.inner, err.to_string()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_category(mut self) -> Self::APP {
|
||||
self.inner.selection.increment_category();
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn decrement_category(mut self) -> Self::APP {
|
||||
self.inner.selection.decrement_category();
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn increment_selection(mut self, delta: Delta) -> Self::APP {
|
||||
self.inner
|
||||
.selection
|
||||
.increment_selection(self.inner.music_hoard.get_collection(), delta);
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn decrement_selection(mut self, delta: Delta) -> Self::APP {
|
||||
self.inner
|
||||
.selection
|
||||
.decrement_selection(self.inner.music_hoard.get_collection(), delta);
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn show_info_overlay(self) -> Self::APP {
|
||||
AppMachine::info(self.inner).into()
|
||||
}
|
||||
|
||||
fn show_reload_menu(self) -> Self::APP {
|
||||
AppMachine::reload(self.inner).into()
|
||||
}
|
||||
|
||||
fn begin_search(mut self) -> Self::APP {
|
||||
let orig = ListSelection::get(&self.inner.selection);
|
||||
self.inner
|
||||
.selection
|
||||
.reset_artist(self.inner.music_hoard.get_collection());
|
||||
AppMachine::search(self.inner, orig).into()
|
||||
}
|
||||
|
||||
fn no_op(self) -> Self::APP {
|
||||
self.into()
|
||||
}
|
||||
}
|
46
src/tui/app/state/critical.rs
Normal file
46
src/tui/app/state/critical.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
state::{AppInner, AppMachine},
|
||||
AppPublic, AppState, IAppInteractCritical,
|
||||
},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppCritical {
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> AppMachine<MH, AppCritical> {
|
||||
pub fn critical<S: Into<String>>(inner: AppInner<MH>, string: S) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: AppCritical {
|
||||
string: string.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> From<AppMachine<MH, AppCritical>> for App<MH> {
|
||||
fn from(machine: AppMachine<MH, AppCritical>) -> Self {
|
||||
AppState::Critical(machine)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppCritical>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<MH, AppCritical>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Critical(&machine.state.string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractCritical for AppMachine<MH, AppCritical> {
|
||||
type APP = App<MH>;
|
||||
|
||||
fn no_op(self) -> Self::APP {
|
||||
self.into()
|
||||
}
|
||||
}
|
46
src/tui/app/state/error.rs
Normal file
46
src/tui/app/state/error.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
state::{AppInner, AppMachine},
|
||||
AppPublic, AppState, IAppInteractError,
|
||||
},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppError {
|
||||
string: String,
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> AppMachine<MH, AppError> {
|
||||
pub fn error<S: Into<String>>(inner: AppInner<MH>, string: S) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: AppError {
|
||||
string: string.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> From<AppMachine<MH, AppError>> for App<MH> {
|
||||
fn from(machine: AppMachine<MH, AppError>) -> Self {
|
||||
AppState::Error(machine)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppError>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<MH, AppError>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Error(&machine.state.string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractError for AppMachine<MH, AppError> {
|
||||
type APP = App<MH>;
|
||||
|
||||
fn dismiss_error(self) -> Self::APP {
|
||||
AppMachine::browse(self.inner).into()
|
||||
}
|
||||
}
|
46
src/tui/app/state/info.rs
Normal file
46
src/tui/app/state/info.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
state::{AppInner, AppMachine},
|
||||
AppPublic, AppState, IAppInteractInfo,
|
||||
},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppInfo;
|
||||
|
||||
impl<MH: IMusicHoard> AppMachine<MH, AppInfo> {
|
||||
pub fn info(inner: AppInner<MH>) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: AppInfo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> From<AppMachine<MH, AppInfo>> for App<MH> {
|
||||
fn from(machine: AppMachine<MH, AppInfo>) -> Self {
|
||||
AppState::Info(machine)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppInfo>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<MH, AppInfo>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Info(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractInfo for AppMachine<MH, AppInfo> {
|
||||
type APP = App<MH>;
|
||||
|
||||
fn hide_info_overlay(self) -> Self::APP {
|
||||
AppMachine::browse(self.inner).into()
|
||||
}
|
||||
|
||||
fn no_op(self) -> Self::APP {
|
||||
self.into()
|
||||
}
|
||||
}
|
778
src/tui/app/state/mod.rs
Normal file
778
src/tui/app/state/mod.rs
Normal file
@ -0,0 +1,778 @@
|
||||
pub mod browse;
|
||||
pub mod critical;
|
||||
pub mod error;
|
||||
pub mod info;
|
||||
pub mod reload;
|
||||
pub mod search;
|
||||
|
||||
use crate::tui::{
|
||||
app::{selection::Selection, AppPublicInner},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppMachine<MH: IMusicHoard, STATE> {
|
||||
inner: AppInner<MH>,
|
||||
state: STATE,
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard, STATE> AppMachine<MH, STATE> {
|
||||
pub fn inner_ref(&self) -> &AppInner<MH> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn inner_mut(&mut self) -> &mut AppInner<MH> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppInner<MH: IMusicHoard> {
|
||||
running: bool,
|
||||
music_hoard: MH,
|
||||
selection: Selection,
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> AppInner<MH> {
|
||||
pub fn new(music_hoard: MH) -> Self {
|
||||
let selection = Selection::new(music_hoard.get_collection());
|
||||
AppInner {
|
||||
running: true,
|
||||
music_hoard,
|
||||
selection,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.running
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
self.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppInner<MH>> for AppPublicInner<'a> {
|
||||
fn from(inner: &'a mut AppInner<MH>) -> Self {
|
||||
AppPublicInner {
|
||||
collection: inner.music_hoard.get_collection(),
|
||||
selection: &mut inner.selection,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use musichoard::collection::Collection;
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
selection::{Category, Delta},
|
||||
AppPublicState, AppState, IAppInteract, IAppInteractBrowse, IAppInteractError,
|
||||
IAppInteractInfo, IAppInteractReload, IAppInteractSearch,
|
||||
},
|
||||
lib::MockIMusicHoard,
|
||||
testmod::COLLECTION,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<BS, IS, RS, SS, ES, CS> AppState<BS, IS, RS, SS, ES, CS> {
|
||||
fn unwrap_browse(self) -> BS {
|
||||
match self {
|
||||
AppState::Browse(browse) => browse,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_info(self) -> IS {
|
||||
match self {
|
||||
AppState::Info(info) => info,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_reload(self) -> RS {
|
||||
match self {
|
||||
AppState::Reload(reload) => reload,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_search(self) -> SS {
|
||||
match self {
|
||||
AppState::Search(search) => search,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_error(self) -> ES {
|
||||
match self {
|
||||
AppState::Error(error) => error,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_critical(self) -> CS {
|
||||
match self {
|
||||
AppState::Critical(critical) => critical,
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn music_hoard(collection: Collection) -> MockIMusicHoard {
|
||||
let mut music_hoard = MockIMusicHoard::new();
|
||||
|
||||
music_hoard
|
||||
.expect_load_from_database()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
music_hoard
|
||||
.expect_rescan_library()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
music_hoard.expect_get_collection().return_const(collection);
|
||||
|
||||
music_hoard
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_is_state() {
|
||||
let state = AppPublicState::Search("get rekt");
|
||||
assert!(state.is_search());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_quit() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_save_to_database()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
|
||||
let app = App::new(music_hoard);
|
||||
assert!(app.is_running());
|
||||
|
||||
let browse = app.unwrap_browse();
|
||||
|
||||
let app = browse.save_and_quit();
|
||||
assert!(!app.is_running());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_quit() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_save_to_database()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
|
||||
let app = App::new(music_hoard);
|
||||
assert!(app.is_running());
|
||||
|
||||
let app = App::Error(AppMachine::error(
|
||||
app.unwrap_browse().inner,
|
||||
String::from("get rekt"),
|
||||
));
|
||||
|
||||
let error = app.unwrap_error();
|
||||
|
||||
let browse = error.dismiss_error().unwrap_browse();
|
||||
|
||||
let app = browse.save_and_quit();
|
||||
assert!(!app.is_running());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn running_force_quit() {
|
||||
let app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||
assert!(app.is_running());
|
||||
|
||||
let app = app.force_quit();
|
||||
assert!(!app.is_running());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_force_quit() {
|
||||
let app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||
assert!(app.is_running());
|
||||
|
||||
let app = App::Error(AppMachine::error(
|
||||
app.unwrap_browse().inner,
|
||||
String::from("get rekt"),
|
||||
));
|
||||
|
||||
let app = app.force_quit();
|
||||
assert!(!app.is_running());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_save_to_database()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
|
||||
let browse = App::new(music_hoard).unwrap_browse();
|
||||
|
||||
browse.save_and_quit().unwrap_browse();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_error() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_save_to_database()
|
||||
.times(1)
|
||||
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||
|
||||
let browse = App::new(music_hoard).unwrap_browse();
|
||||
|
||||
browse.save_and_quit().unwrap_error();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_error() {
|
||||
let mut music_hoard = MockIMusicHoard::new();
|
||||
|
||||
music_hoard
|
||||
.expect_load_from_database()
|
||||
.times(1)
|
||||
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||
music_hoard.expect_get_collection().return_const(vec![]);
|
||||
|
||||
let app = App::new(music_hoard);
|
||||
|
||||
assert!(app.is_running());
|
||||
app.unwrap_critical();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modifiers() {
|
||||
let app = App::new(music_hoard(COLLECTION.to_owned()));
|
||||
assert!(app.is_running());
|
||||
|
||||
let browse = app.unwrap_browse();
|
||||
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(1));
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(1));
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.decrement_category().unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(1));
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.decrement_category().unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.decrement_category().unwrap_browse();
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.decrement_category().unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(1));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_tracks() {
|
||||
let mut collection = COLLECTION.to_owned();
|
||||
collection[0].albums[0].tracks = vec![];
|
||||
|
||||
let app = App::new(music_hoard(collection));
|
||||
assert!(app.is_running());
|
||||
|
||||
let browse = app.unwrap_browse();
|
||||
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_albums() {
|
||||
let mut collection = COLLECTION.to_owned();
|
||||
collection[0].albums = vec![];
|
||||
|
||||
let app = App::new(music_hoard(collection));
|
||||
assert!(app.is_running());
|
||||
|
||||
let browse = app.unwrap_browse();
|
||||
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), Some(0));
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_artists() {
|
||||
let app = App::new(music_hoard(vec![]));
|
||||
assert!(app.is_running());
|
||||
|
||||
let browse = app.unwrap_browse();
|
||||
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Artist);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Album);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
|
||||
let browse = browse.decrement_selection(Delta::Line).unwrap_browse();
|
||||
let selection = &browse.inner.selection;
|
||||
assert_eq!(selection.active, Category::Track);
|
||||
assert_eq!(selection.artist.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.state.list.selected(), None);
|
||||
assert_eq!(selection.artist.album.track.state.list.selected(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn info_overlay() {
|
||||
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||
|
||||
let info = browse.show_info_overlay().unwrap_info();
|
||||
|
||||
info.hide_info_overlay().unwrap_browse();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_hide_menu() {
|
||||
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||
|
||||
let reload = browse.show_reload_menu().unwrap_reload();
|
||||
|
||||
reload.hide_reload_menu().unwrap_browse();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_database() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_load_from_database()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
|
||||
let browse = App::new(music_hoard).unwrap_browse();
|
||||
|
||||
let reload = browse.show_reload_menu().unwrap_reload();
|
||||
|
||||
reload.reload_database().unwrap_browse();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_library() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_rescan_library()
|
||||
.times(1)
|
||||
.return_once(|| Ok(()));
|
||||
|
||||
let browse = App::new(music_hoard).unwrap_browse();
|
||||
|
||||
let reload = browse.show_reload_menu().unwrap_reload();
|
||||
|
||||
reload.reload_library().unwrap_browse();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reload_error() {
|
||||
let mut music_hoard = music_hoard(COLLECTION.to_owned());
|
||||
|
||||
music_hoard
|
||||
.expect_load_from_database()
|
||||
.times(1)
|
||||
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
|
||||
|
||||
let browse = App::new(music_hoard).unwrap_browse();
|
||||
|
||||
let reload = browse.show_reload_menu().unwrap_reload();
|
||||
|
||||
let error = reload.reload_database().unwrap_error();
|
||||
|
||||
error.dismiss_error().unwrap_browse();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search() {
|
||||
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||
|
||||
let search = browse.begin_search().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.append_character('a').unwrap_search();
|
||||
let search = search.append_character('l').unwrap_search();
|
||||
let search = search.append_character('b').unwrap_search();
|
||||
let search = search.append_character('u').unwrap_search();
|
||||
let search = search.append_character('m').unwrap_search();
|
||||
let search = search.append_character('_').unwrap_search();
|
||||
let search = search.append_character('a').unwrap_search();
|
||||
let search = search.append_character('r').unwrap_search();
|
||||
let search = search.append_character('t').unwrap_search();
|
||||
let search = search.append_character('i').unwrap_search();
|
||||
let search = search.append_character('s').unwrap_search();
|
||||
let search = search.append_character('t').unwrap_search();
|
||||
let search = search.append_character(' ').unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.append_character('\'').unwrap_search();
|
||||
let search = search.append_character('c').unwrap_search();
|
||||
let search = search.append_character('\'').unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
let search = search.step_back().unwrap_search();
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.append_character('\'').unwrap_search();
|
||||
let search = search.append_character('b').unwrap_search();
|
||||
let search = search.append_character('\'').unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||
|
||||
let browse = search.finish_search().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_next() {
|
||||
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||
|
||||
let search = browse.begin_search().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.append_character('a').unwrap_search();
|
||||
|
||||
let search = search.search_next().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||
|
||||
let search = search.search_next().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||
|
||||
let search = search.search_next().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||
|
||||
let search = search.search_next().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(3));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(1));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_search() {
|
||||
let browse = App::new(music_hoard(COLLECTION.to_owned())).unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||
|
||||
let search = browse.begin_search().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(0));
|
||||
|
||||
let search = search.append_character('a').unwrap_search();
|
||||
let search = search.append_character('l').unwrap_search();
|
||||
let search = search.append_character('b').unwrap_search();
|
||||
let search = search.append_character('u').unwrap_search();
|
||||
let search = search.append_character('m').unwrap_search();
|
||||
let search = search.append_character('_').unwrap_search();
|
||||
let search = search.append_character('a').unwrap_search();
|
||||
let search = search.append_character('r').unwrap_search();
|
||||
let search = search.append_character('t').unwrap_search();
|
||||
let search = search.append_character('i').unwrap_search();
|
||||
let search = search.append_character('s').unwrap_search();
|
||||
let search = search.append_character('t').unwrap_search();
|
||||
let search = search.append_character(' ').unwrap_search();
|
||||
let search = search.append_character('\'').unwrap_search();
|
||||
let search = search.append_character('c').unwrap_search();
|
||||
let search = search.append_character('\'').unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), Some(2));
|
||||
|
||||
let browse = search.cancel_search().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_search() {
|
||||
let browse = App::new(music_hoard(vec![])).unwrap_browse();
|
||||
|
||||
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
|
||||
let browse = browse.increment_category().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), None);
|
||||
|
||||
let search = browse.begin_search().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||
|
||||
let search = search.append_character('a').unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||
|
||||
let search = search.search_next().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||
|
||||
let search = search.step_back().unwrap_search();
|
||||
|
||||
assert_eq!(search.inner.selection.active, Category::Album);
|
||||
assert_eq!(search.inner.selection.artist.state.list.selected(), None);
|
||||
|
||||
let browse = search.cancel_search().unwrap_browse();
|
||||
|
||||
assert_eq!(browse.inner.selection.active, Category::Album);
|
||||
assert_eq!(browse.inner.selection.artist.state.list.selected(), None);
|
||||
}
|
||||
}
|
82
src/tui/app/state/reload.rs
Normal file
82
src/tui/app/state/reload.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
selection::IdSelection,
|
||||
state::{AppInner, AppMachine},
|
||||
AppPublic, AppState, IAppInteractReload,
|
||||
},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppReload;
|
||||
|
||||
impl<MH: IMusicHoard> AppMachine<MH, AppReload> {
|
||||
pub fn reload(inner: AppInner<MH>) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: AppReload,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> From<AppMachine<MH, AppReload>> for App<MH> {
|
||||
fn from(machine: AppMachine<MH, AppReload>) -> Self {
|
||||
AppState::Reload(machine)
|
||||
}
|
||||
}
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppReload>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<MH, AppReload>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Reload(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractReload for AppMachine<MH, AppReload> {
|
||||
type APP = App<MH>;
|
||||
|
||||
fn reload_library(mut self) -> Self::APP {
|
||||
let previous = IdSelection::get(
|
||||
self.inner.music_hoard.get_collection(),
|
||||
&self.inner.selection,
|
||||
);
|
||||
let result = self.inner.music_hoard.rescan_library();
|
||||
self.refresh(previous, result)
|
||||
}
|
||||
|
||||
fn reload_database(mut self) -> Self::APP {
|
||||
let previous = IdSelection::get(
|
||||
self.inner.music_hoard.get_collection(),
|
||||
&self.inner.selection,
|
||||
);
|
||||
let result = self.inner.music_hoard.load_from_database();
|
||||
self.refresh(previous, result)
|
||||
}
|
||||
|
||||
fn hide_reload_menu(self) -> Self::APP {
|
||||
AppMachine::browse(self.inner).into()
|
||||
}
|
||||
|
||||
fn no_op(self) -> Self::APP {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
trait IAppInteractReloadPrivate<MH: IMusicHoard> {
|
||||
fn refresh(self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH>;
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractReloadPrivate<MH> for AppMachine<MH, AppReload> {
|
||||
fn refresh(mut self, previous: IdSelection, result: Result<(), musichoard::Error>) -> App<MH> {
|
||||
match result {
|
||||
Ok(()) => {
|
||||
self.inner
|
||||
.selection
|
||||
.select_by_id(self.inner.music_hoard.get_collection(), previous);
|
||||
AppMachine::browse(self.inner).into()
|
||||
}
|
||||
Err(err) => AppMachine::error(self.inner, err.to_string()).into(),
|
||||
}
|
||||
}
|
||||
}
|
100
src/tui/app/state/search.rs
Normal file
100
src/tui/app/state/search.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use crate::tui::{
|
||||
app::{
|
||||
app::App,
|
||||
selection::ListSelection,
|
||||
state::{AppInner, AppMachine},
|
||||
AppState, IAppInteractSearch, AppPublic,
|
||||
},
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub struct AppSearch {
|
||||
string: String,
|
||||
orig: ListSelection,
|
||||
memo: Vec<AppSearchMemo>,
|
||||
}
|
||||
|
||||
struct AppSearchMemo {
|
||||
index: Option<usize>,
|
||||
char: bool,
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> AppMachine<MH, AppSearch> {
|
||||
pub fn search(inner: AppInner<MH>, orig: ListSelection) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: AppSearch {
|
||||
string: String::new(),
|
||||
orig,
|
||||
memo: vec![],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> From<AppMachine<MH, AppSearch>> for App<MH> {
|
||||
fn from(machine: AppMachine<MH, AppSearch>) -> Self {
|
||||
AppState::Search(machine)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, MH: IMusicHoard> From<&'a mut AppMachine<MH, AppSearch>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<MH, AppSearch>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Search(&machine.state.string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
|
||||
type APP = App<MH>;
|
||||
|
||||
fn append_character(mut self, ch: char) -> Self::APP {
|
||||
let collection = self.inner.music_hoard.get_collection();
|
||||
self.state.string.push(ch);
|
||||
let index =
|
||||
self.inner
|
||||
.selection
|
||||
.incremental_artist_search(collection, &self.state.string, false);
|
||||
self.state.memo.push(AppSearchMemo { index, char: true });
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn search_next(mut self) -> Self::APP {
|
||||
let collection = self.inner.music_hoard.get_collection();
|
||||
if !self.state.string.is_empty() {
|
||||
let index = self.inner.selection.incremental_artist_search(
|
||||
collection,
|
||||
&self.state.string,
|
||||
true,
|
||||
);
|
||||
self.state.memo.push(AppSearchMemo { index, char: false });
|
||||
}
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn step_back(mut self) -> Self::APP {
|
||||
let collection = self.inner.music_hoard.get_collection();
|
||||
if let Some(memo) = self.state.memo.pop() {
|
||||
if memo.char {
|
||||
self.state.string.pop();
|
||||
}
|
||||
self.inner.selection.select_artist(collection, memo.index);
|
||||
}
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn finish_search(self) -> Self::APP {
|
||||
AppMachine::browse(self.inner).into()
|
||||
}
|
||||
|
||||
fn cancel_search(mut self) -> Self::APP {
|
||||
self.inner.selection.select_by_list(self.state.orig);
|
||||
AppMachine::browse(self.inner).into()
|
||||
}
|
||||
|
||||
fn no_op(self) -> Self::APP {
|
||||
self.into()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user