188---add-option-for-manual-input-during-fetch (#214)
Some checks failed
Cargo CI / Lint (push) Waiting to run
Cargo CI / Build and Test (push) Has been cancelled

PR 1 for #188

Reviewed-on: #214
This commit is contained in:
Wojciech Kozlowski 2024-09-13 21:27:31 +02:00
parent 9d1caffd9c
commit f2996903b2
21 changed files with 429 additions and 404 deletions

View File

@ -4,25 +4,25 @@ use crate::tui::app::{
AppPublic, AppState, IAppInteractBrowse,
};
pub struct AppBrowse;
pub struct BrowseState;
impl AppMachine<AppBrowse> {
pub fn browse(inner: AppInner) -> Self {
impl AppMachine<BrowseState> {
pub fn browse_state(inner: AppInner) -> Self {
AppMachine {
inner,
state: AppBrowse,
state: BrowseState,
}
}
}
impl From<AppMachine<AppBrowse>> for App {
fn from(machine: AppMachine<AppBrowse>) -> Self {
impl From<AppMachine<BrowseState>> for App {
fn from(machine: AppMachine<BrowseState>) -> Self {
AppState::Browse(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppBrowse>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppBrowse>) -> Self {
impl<'a> From<&'a mut AppMachine<BrowseState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<BrowseState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Browse(()),
@ -30,7 +30,7 @@ impl<'a> From<&'a mut AppMachine<AppBrowse>> for AppPublic<'a> {
}
}
impl IAppInteractBrowse for AppMachine<AppBrowse> {
impl IAppInteractBrowse for AppMachine<BrowseState> {
type APP = App;
fn quit(mut self) -> Self::APP {
@ -63,11 +63,11 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
}
fn show_info_overlay(self) -> Self::APP {
AppMachine::info(self.inner).into()
AppMachine::info_state(self.inner).into()
}
fn show_reload_menu(self) -> Self::APP {
AppMachine::reload(self.inner).into()
AppMachine::reload_state(self.inner).into()
}
fn begin_search(mut self) -> Self::APP {
@ -75,7 +75,7 @@ impl IAppInteractBrowse for AppMachine<AppBrowse> {
self.inner
.selection
.reset(self.inner.music_hoard.get_collection());
AppMachine::search(self.inner, orig).into()
AppMachine::search_state(self.inner, orig).into()
}
fn fetch_musicbrainz(self) -> Self::APP {
@ -88,7 +88,7 @@ mod tests {
use crate::tui::{
app::{
machine::tests::{inner, inner_with_mb, music_hoard},
Category, IAppAccess, IAppInteract,
Category, IApp, IAppAccess,
},
lib::interface::musicbrainz::MockIMusicBrainz,
testmod::COLLECTION,
@ -100,7 +100,7 @@ mod tests {
fn quit() {
let music_hoard = music_hoard(vec![]);
let browse = AppMachine::browse(inner(music_hoard));
let browse = AppMachine::browse_state(inner(music_hoard));
let app = browse.quit();
assert!(!app.is_running());
@ -109,7 +109,7 @@ mod tests {
#[test]
fn increment_decrement() {
let mut browse = AppMachine::browse(inner(music_hoard(COLLECTION.to_owned())));
let mut browse = AppMachine::browse_state(inner(music_hoard(COLLECTION.to_owned())));
let sel = &browse.inner.selection;
assert_eq!(sel.category(), Category::Artist);
assert_eq!(sel.selected(), Some(0));
@ -147,21 +147,21 @@ mod tests {
#[test]
fn show_info_overlay() {
let browse = AppMachine::browse(inner(music_hoard(vec![])));
let browse = AppMachine::browse_state(inner(music_hoard(vec![])));
let app = browse.show_info_overlay();
app.unwrap_info();
}
#[test]
fn show_reload_menu() {
let browse = AppMachine::browse(inner(music_hoard(vec![])));
let browse = AppMachine::browse_state(inner(music_hoard(vec![])));
let app = browse.show_reload_menu();
app.unwrap_reload();
}
#[test]
fn begin_search() {
let browse = AppMachine::browse(inner(music_hoard(vec![])));
let browse = AppMachine::browse_state(inner(music_hoard(vec![])));
let app = browse.begin_search();
app.unwrap_search();
}
@ -169,7 +169,8 @@ mod tests {
#[test]
fn fetch_musicbrainz() {
let mb_api = MockIMusicBrainz::new();
let browse = AppMachine::browse(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
let browse =
AppMachine::browse_state(inner_with_mb(music_hoard(COLLECTION.to_owned()), mb_api));
// Use the second artist for this test.
let browse = browse.increment_selection(Delta::Line).unwrap_browse();
@ -179,7 +180,7 @@ mod tests {
// Because of fetch's threaded behaviour, this unit test cannot expect one or the other.
assert!(
matches!(public.state, AppState::Matches(_))
matches!(public.state, AppState::Match(_))
|| matches!(public.state, AppState::Fetch(_))
);
}

View File

@ -3,29 +3,29 @@ use crate::tui::app::{
AppPublic, AppState,
};
pub struct AppCritical {
pub struct CriticalState {
string: String,
}
impl AppMachine<AppCritical> {
pub fn critical<S: Into<String>>(inner: AppInner, string: S) -> Self {
impl AppMachine<CriticalState> {
pub fn critical_state<S: Into<String>>(inner: AppInner, string: S) -> Self {
AppMachine {
inner,
state: AppCritical {
state: CriticalState {
string: string.into(),
},
}
}
}
impl From<AppMachine<AppCritical>> for App {
fn from(machine: AppMachine<AppCritical>) -> Self {
impl From<AppMachine<CriticalState>> for App {
fn from(machine: AppMachine<CriticalState>) -> Self {
AppState::Critical(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppCritical>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppCritical>) -> Self {
impl<'a> From<&'a mut AppMachine<CriticalState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<CriticalState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Critical(&machine.state.string),

View File

@ -3,29 +3,29 @@ use crate::tui::app::{
AppPublic, AppState, IAppInteractError,
};
pub struct AppError {
pub struct ErrorState {
string: String,
}
impl AppMachine<AppError> {
pub fn error<S: Into<String>>(inner: AppInner, string: S) -> Self {
impl AppMachine<ErrorState> {
pub fn error_state<S: Into<String>>(inner: AppInner, string: S) -> Self {
AppMachine {
inner,
state: AppError {
state: ErrorState {
string: string.into(),
},
}
}
}
impl From<AppMachine<AppError>> for App {
fn from(machine: AppMachine<AppError>) -> Self {
impl From<AppMachine<ErrorState>> for App {
fn from(machine: AppMachine<ErrorState>) -> Self {
AppState::Error(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppError>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppError>) -> Self {
impl<'a> From<&'a mut AppMachine<ErrorState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<ErrorState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Error(&machine.state.string),
@ -33,11 +33,11 @@ impl<'a> From<&'a mut AppMachine<AppError>> for AppPublic<'a> {
}
}
impl IAppInteractError for AppMachine<AppError> {
impl IAppInteractError for AppMachine<ErrorState> {
type APP = App;
fn dismiss_error(self) -> Self::APP {
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
}
@ -49,7 +49,7 @@ mod tests {
#[test]
fn dismiss_error() {
let error = AppMachine::error(inner(music_hoard(vec![])), "get rekt");
let error = AppMachine::error_state(inner(music_hoard(vec![])), "get rekt");
let app = error.dismiss_error();
app.unwrap_browse();
}

View File

@ -15,31 +15,31 @@ use musichoard::collection::{
use crate::tui::{
app::{
machine::{App, AppInner, AppMachine},
AppMatchesInfo, AppPublic, AppState, IAppEventFetch, IAppInteractFetch,
AppPublic, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo,
},
event::{Event, EventSender},
lib::interface::musicbrainz::{self, Error as MbError, IMusicBrainz},
};
use super::matches::AppMatches;
use super::match_state::MatchState;
pub struct AppFetch {
pub struct FetchState {
fetch_rx: FetchReceiver,
}
impl AppFetch {
impl FetchState {
pub fn new(fetch_rx: FetchReceiver) -> Self {
AppFetch { fetch_rx }
FetchState { fetch_rx }
}
}
pub type FetchError = MbError;
pub type FetchResult = Result<AppMatchesInfo, FetchError>;
pub type FetchResult = Result<MatchStateInfo, FetchError>;
pub type FetchSender = mpsc::Sender<FetchResult>;
pub type FetchReceiver = mpsc::Receiver<FetchResult>;
impl AppMachine<AppFetch> {
fn fetch(inner: AppInner, state: AppFetch) -> Self {
impl AppMachine<FetchState> {
fn fetch_state(inner: AppInner, state: FetchState) -> Self {
AppMachine { inner, state }
}
@ -47,38 +47,40 @@ impl AppMachine<AppFetch> {
let coll = inner.music_hoard.get_collection();
let artist = match inner.selection.state_artist(coll) {
Some(artist_state) => &coll[artist_state.index],
None => return AppMachine::error(inner, "cannot fetch: no artist selected").into(),
None => {
return AppMachine::error_state(inner, "cannot fetch: no artist selected").into()
}
};
let (fetch_tx, fetch_rx) = mpsc::channel::<FetchResult>();
Self::spawn_fetch_thread(&inner, artist, fetch_tx);
let fetch = AppFetch::new(fetch_rx);
let fetch = FetchState::new(fetch_rx);
AppMachine::app_fetch(inner, fetch, true)
}
pub fn app_fetch_next(inner: AppInner, fetch: AppFetch) -> App {
pub fn app_fetch_next(inner: AppInner, fetch: FetchState) -> App {
Self::app_fetch(inner, fetch, false)
}
fn app_fetch(inner: AppInner, fetch: AppFetch, first: bool) -> App {
fn app_fetch(inner: AppInner, fetch: FetchState, first: bool) -> App {
match fetch.fetch_rx.try_recv() {
Ok(fetch_result) => match fetch_result {
Ok(next_match) => {
let current = Some(next_match);
AppMachine::matches(inner, AppMatches::new(current, fetch)).into()
AppMachine::match_state(inner, MatchState::new(current, fetch)).into()
}
Err(fetch_err) => {
AppMachine::error(inner, format!("fetch failed: {fetch_err}")).into()
AppMachine::error_state(inner, format!("fetch failed: {fetch_err}")).into()
}
},
Err(recv_err) => match recv_err {
TryRecvError::Empty => AppMachine::fetch(inner, fetch).into(),
TryRecvError::Empty => AppMachine::fetch_state(inner, fetch).into(),
TryRecvError::Disconnected => {
if first {
AppMachine::matches(inner, AppMatches::new(None, fetch)).into()
AppMachine::match_state(inner, MatchState::new(None, fetch)).into()
} else {
AppMachine::browse(inner).into()
AppMachine::browse_state(inner).into()
}
}
},
@ -114,7 +116,7 @@ impl AppMachine<AppFetch> {
artist: ArtistMeta,
) {
let result = musicbrainz.lock().unwrap().search_artist(&artist);
let result = result.map(|list| AppMatchesInfo::artist(artist, list));
let result = result.map(|list| MatchStateInfo::artist(artist, list));
Self::send_fetch_result(&fetch_tx, &events, result).ok();
}
@ -133,7 +135,7 @@ impl AppMachine<AppFetch> {
}
let result = musicbrainz.search_release_group(&arid, &album);
let result = result.map(|list| AppMatchesInfo::album(album, list));
let result = result.map(|list| MatchStateInfo::album(album, list));
if Self::send_fetch_result(&fetch_tx, &events, result).is_err() {
return;
};
@ -147,7 +149,7 @@ impl AppMachine<AppFetch> {
fn send_fetch_result(
fetch_tx: &FetchSender,
events: &EventSender,
result: Result<AppMatchesInfo, musicbrainz::Error>,
result: Result<MatchStateInfo, musicbrainz::Error>,
) -> Result<(), ()> {
// If receiver disconnects just drop the rest.
fetch_tx.send(result).map_err(|_| ())?;
@ -160,14 +162,14 @@ impl AppMachine<AppFetch> {
}
}
impl From<AppMachine<AppFetch>> for App {
fn from(machine: AppMachine<AppFetch>) -> Self {
impl From<AppMachine<FetchState>> for App {
fn from(machine: AppMachine<FetchState>) -> Self {
AppState::Fetch(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppFetch>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppFetch>) -> Self {
impl<'a> From<&'a mut AppMachine<FetchState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<FetchState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Fetch(()),
@ -175,15 +177,15 @@ impl<'a> From<&'a mut AppMachine<AppFetch>> for AppPublic<'a> {
}
}
impl IAppInteractFetch for AppMachine<AppFetch> {
impl IAppInteractFetch for AppMachine<FetchState> {
type APP = App;
fn abort(self) -> Self::APP {
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
}
impl IAppEventFetch for AppMachine<AppFetch> {
impl IAppEventFetch for AppMachine<FetchState> {
type APP = App;
fn fetch_result_ready(self) -> Self::APP {
@ -199,7 +201,7 @@ mod tests {
use crate::tui::{
app::{
machine::tests::{inner, music_hoard},
AppAlbumMatches, AppArtistMatches, IAppInteract,
AlbumMatches, ArtistMatches, IApp,
},
event::EventReceiver,
lib::interface::musicbrainz::{self, Match, MockIMusicBrainz},
@ -285,7 +287,7 @@ mod tests {
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
let result = fetch_rx.try_recv().unwrap();
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
let expected = Ok(MatchStateInfo::Album(AlbumMatches {
matching: album_1.clone(),
list: matches_1.iter().cloned().map(Into::into).collect(),
}));
@ -293,7 +295,7 @@ mod tests {
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
let result = fetch_rx.try_recv().unwrap();
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
let expected = Ok(MatchStateInfo::Album(AlbumMatches {
matching: album_4.clone(),
list: matches_4.iter().cloned().map(Into::into).collect(),
}));
@ -343,7 +345,7 @@ mod tests {
assert_eq!(events_rx.try_recv().unwrap(), Event::FetchResultReady);
let result = fetch_rx.try_recv().unwrap();
let expected = Ok(AppMatchesInfo::Artist(AppArtistMatches {
let expected = Ok(MatchStateInfo::Artist(ArtistMatches {
matching: artist.clone(),
list: matches.iter().cloned().map(Into::into).collect(),
}));
@ -391,7 +393,7 @@ mod tests {
handle.join().unwrap();
let result = fetch_rx.try_recv().unwrap();
let expected = Ok(AppMatchesInfo::Album(AppAlbumMatches {
let expected = Ok(MatchStateInfo::Album(AlbumMatches {
matching: album_1.clone(),
list: matches_1.iter().cloned().map(Into::into).collect(),
}));
@ -405,13 +407,13 @@ mod tests {
let (tx, rx) = mpsc::channel::<FetchResult>();
let artist = COLLECTION[3].meta.clone();
let fetch_result = Ok(AppMatchesInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
tx.send(fetch_result).unwrap();
let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = AppFetch::new(rx);
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true);
assert!(matches!(app, AppState::Matches(_)));
assert!(matches!(app, AppState::Match(_)));
}
#[test]
@ -422,7 +424,7 @@ mod tests {
tx.send(fetch_result).unwrap();
let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = AppFetch::new(rx);
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true);
assert!(matches!(app, AppState::Error(_)));
}
@ -432,7 +434,7 @@ mod tests {
let (_tx, rx) = mpsc::channel::<FetchResult>();
let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = AppFetch::new(rx);
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true);
assert!(matches!(app, AppState::Fetch(_)));
}
@ -442,16 +444,16 @@ mod tests {
let (_, rx) = mpsc::channel::<FetchResult>();
let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = AppFetch::new(rx);
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true);
assert!(matches!(app, AppState::Matches(_)));
assert!(matches!(app, AppState::Match(_)));
}
#[test]
fn recv_err_disconnected_next() {
let (_, rx) = mpsc::channel::<FetchResult>();
let fetch = AppFetch::new(rx);
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch_next(inner(music_hoard(COLLECTION.clone())), fetch);
assert!(matches!(app, AppState::Browse(_)));
}
@ -461,24 +463,24 @@ mod tests {
let (tx, rx) = mpsc::channel::<FetchResult>();
let inner = inner(music_hoard(COLLECTION.clone()));
let fetch = AppFetch::new(rx);
let fetch = FetchState::new(rx);
let app = AppMachine::app_fetch(inner, fetch, true);
assert!(matches!(app, AppState::Fetch(_)));
let artist = COLLECTION[3].meta.clone();
let fetch_result = Ok(AppMatchesInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
let fetch_result = Ok(MatchStateInfo::artist::<Match<ArtistMeta>>(artist, vec![]));
tx.send(fetch_result).unwrap();
let app = app.unwrap_fetch().fetch_result_ready();
assert!(matches!(app, AppState::Matches(_)));
assert!(matches!(app, AppState::Match(_)));
}
#[test]
fn abort() {
let (_, rx) = mpsc::channel::<FetchResult>();
let fetch = AppFetch::new(rx);
let app = AppMachine::fetch(inner(music_hoard(COLLECTION.clone())), fetch);
let fetch = FetchState::new(rx);
let app = AppMachine::fetch_state(inner(music_hoard(COLLECTION.clone())), fetch);
let app = app.abort();
assert!(matches!(app, AppState::Browse(_)));

View File

@ -3,25 +3,25 @@ use crate::tui::app::{
AppPublic, AppState, IAppInteractInfo,
};
pub struct AppInfo;
pub struct InfoState;
impl AppMachine<AppInfo> {
pub fn info(inner: AppInner) -> Self {
impl AppMachine<InfoState> {
pub fn info_state(inner: AppInner) -> Self {
AppMachine {
inner,
state: AppInfo,
state: InfoState,
}
}
}
impl From<AppMachine<AppInfo>> for App {
fn from(machine: AppMachine<AppInfo>) -> Self {
impl From<AppMachine<InfoState>> for App {
fn from(machine: AppMachine<InfoState>) -> Self {
AppState::Info(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppInfo>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppInfo>) -> Self {
impl<'a> From<&'a mut AppMachine<InfoState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<InfoState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Info(()),
@ -29,11 +29,11 @@ impl<'a> From<&'a mut AppMachine<AppInfo>> for AppPublic<'a> {
}
}
impl IAppInteractInfo for AppMachine<AppInfo> {
impl IAppInteractInfo for AppMachine<InfoState> {
type APP = App;
fn hide_info_overlay(self) -> Self::APP {
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
}
@ -45,7 +45,7 @@ mod tests {
#[test]
fn hide_info_overlay() {
let info = AppMachine::info(inner(music_hoard(vec![])));
let info = AppMachine::info_state(inner(music_hoard(vec![])));
let app = info.hide_info_overlay();
app.unwrap_browse();
}

View File

@ -2,13 +2,13 @@ use std::cmp;
use crate::tui::app::{
machine::{App, AppInner, AppMachine},
AppAlbumMatches, AppArtistMatches, AppMatchesInfo, AppPublic, AppPublicMatches, AppState,
IAppInteractMatches, MatchOption, WidgetState,
AlbumMatches, AppPublic, AppState, ArtistMatches, IAppInteractMatch, MatchOption,
MatchStateInfo, MatchStatePublic, WidgetState,
};
use super::fetch::AppFetch;
use super::fetch_state::FetchState;
impl AppArtistMatches {
impl ArtistMatches {
fn len(&self) -> usize {
self.list.len()
}
@ -18,7 +18,7 @@ impl AppArtistMatches {
}
}
impl AppAlbumMatches {
impl AlbumMatches {
fn len(&self) -> usize {
self.list.len()
}
@ -28,7 +28,7 @@ impl AppAlbumMatches {
}
}
impl AppMatchesInfo {
impl MatchStateInfo {
fn len(&self) -> usize {
match self {
Self::Artist(a) => a.len(),
@ -44,20 +44,20 @@ impl AppMatchesInfo {
}
}
pub struct AppMatches {
current: Option<AppMatchesInfo>,
pub struct MatchState {
current: Option<MatchStateInfo>,
state: WidgetState,
fetch: AppFetch,
fetch: FetchState,
}
impl AppMatches {
pub fn new(mut current: Option<AppMatchesInfo>, fetch: AppFetch) -> Self {
impl MatchState {
pub fn new(mut current: Option<MatchStateInfo>, fetch: FetchState) -> Self {
let mut state = WidgetState::default();
if let Some(ref mut current) = current {
state.list.select(Some(0));
current.push_cannot_have_mbid();
}
AppMatches {
MatchState {
current,
state,
fetch,
@ -65,31 +65,31 @@ impl AppMatches {
}
}
impl AppMachine<AppMatches> {
pub fn matches(inner: AppInner, state: AppMatches) -> Self {
impl AppMachine<MatchState> {
pub fn match_state(inner: AppInner, state: MatchState) -> Self {
AppMachine { inner, state }
}
}
impl From<AppMachine<AppMatches>> for App {
fn from(machine: AppMachine<AppMatches>) -> Self {
AppState::Matches(machine)
impl From<AppMachine<MatchState>> for App {
fn from(machine: AppMachine<MatchState>) -> Self {
AppState::Match(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppMatches>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppMatches>) -> Self {
impl<'a> From<&'a mut AppMachine<MatchState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<MatchState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Matches(AppPublicMatches {
matches: machine.state.current.as_ref().map(Into::into),
state: AppState::Match(MatchStatePublic {
info: machine.state.current.as_ref().map(Into::into),
state: &mut machine.state.state,
}),
}
}
}
impl IAppInteractMatches for AppMachine<AppMatches> {
impl IAppInteractMatch for AppMachine<MatchState> {
type APP = App;
fn prev_match(mut self) -> Self::APP {
@ -120,7 +120,7 @@ impl IAppInteractMatches for AppMachine<AppMatches> {
}
fn abort(self) -> Self::APP {
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
}
@ -153,7 +153,7 @@ mod tests {
}
}
fn artist_match() -> AppMatchesInfo {
fn artist_match() -> MatchStateInfo {
let artist = ArtistMeta::new(ArtistId::new("Artist"));
let artist_1 = artist.clone();
@ -164,10 +164,10 @@ mod tests {
artist_match_2.disambiguation = Some(String::from("some disambiguation"));
let list = vec![artist_match_1.clone(), artist_match_2.clone()];
AppMatchesInfo::artist(artist, list)
MatchStateInfo::artist(artist, list)
}
fn album_match() -> AppMatchesInfo {
fn album_match() -> MatchStateInfo {
let album = AlbumMeta::new(
AlbumId::new("Album"),
AlbumDate::new(Some(1990), Some(5), None),
@ -184,21 +184,21 @@ mod tests {
let album_match_2 = Match::new(100, album_2);
let list = vec![album_match_1.clone(), album_match_2.clone()];
AppMatchesInfo::album(album, list)
MatchStateInfo::album(album, list)
}
fn fetch() -> AppFetch {
fn fetch_state() -> FetchState {
let (_, rx) = mpsc::channel();
AppFetch::new(rx)
FetchState::new(rx)
}
fn matches(matches_info: Option<AppMatchesInfo>) -> AppMatches {
AppMatches::new(matches_info, fetch())
fn match_state(matches_info: Option<MatchStateInfo>) -> MatchState {
MatchState::new(matches_info, fetch_state())
}
#[test]
fn create_empty() {
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(None));
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
let widget_state = WidgetState::default();
@ -207,18 +207,18 @@ mod tests {
let mut app: App = matches.into();
let public = app.get();
let public_matches = public.state.unwrap_matches();
let public_matches = public.state.unwrap_match();
assert_eq!(public_matches.matches, None);
assert_eq!(public_matches.info, None);
assert_eq!(public_matches.state, &widget_state);
}
#[test]
fn create_nonempty() {
let mut album_match = album_match();
let matches = AppMachine::matches(
let matches = AppMachine::match_state(
inner(music_hoard(vec![])),
matches(Some(album_match.clone())),
match_state(Some(album_match.clone())),
);
album_match.push_cannot_have_mbid();
@ -230,19 +230,19 @@ mod tests {
let mut app: App = matches.into();
let public = app.get();
let public_matches = public.state.unwrap_matches();
let public_matches = public.state.unwrap_match();
assert_eq!(public_matches.matches, Some(&album_match));
assert_eq!(public_matches.info, Some(&album_match));
assert_eq!(public_matches.state, &widget_state);
}
fn matches_flow(mut matches_info: AppMatchesInfo) {
fn match_state_flow(mut matches_info: MatchStateInfo) {
// tx must exist for rx to return Empty rather than Disconnected.
#[allow(unused_variables)]
let (tx, rx) = mpsc::channel();
let app_matches = AppMatches::new(Some(matches_info.clone()), AppFetch::new(rx));
let app_matches = MatchState::new(Some(matches_info.clone()), FetchState::new(rx));
let matches = AppMachine::matches(inner(music_hoard(vec![])), app_matches);
let matches = AppMachine::match_state(inner(music_hoard(vec![])), app_matches);
matches_info.push_cannot_have_mbid();
let mut widget_state = WidgetState::default();
@ -251,23 +251,23 @@ mod tests {
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state, widget_state);
let matches = matches.prev_match().unwrap_matches();
let matches = matches.prev_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(0));
let matches = matches.next_match().unwrap_matches();
let matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(1));
// Next is CannotHaveMBID
let matches = matches.next_match().unwrap_matches();
let matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(2));
let matches = matches.next_match().unwrap_matches();
let matches = matches.next_match().unwrap_match();
assert_eq!(matches.state.current.as_ref(), Some(&matches_info));
assert_eq!(matches.state.state.list.selected(), Some(2));
@ -278,20 +278,20 @@ mod tests {
#[test]
fn artist_matches_flow() {
matches_flow(artist_match());
match_state_flow(artist_match());
}
#[test]
fn album_matches_flow() {
matches_flow(album_match());
match_state_flow(album_match());
}
#[test]
fn matches_abort() {
fn abort() {
let mut album_match = album_match();
let matches = AppMachine::matches(
let matches = AppMachine::match_state(
inner(music_hoard(vec![])),
matches(Some(album_match.clone())),
match_state(Some(album_match.clone())),
);
album_match.push_cannot_have_mbid();
@ -305,10 +305,10 @@ mod tests {
}
#[test]
fn matches_select_empty() {
fn select_empty() {
// Note that what really matters in this test is actually that the transmit channel has
// disconnected and so the receive within AppFetch concludes there are no more matches.
let matches = AppMachine::matches(inner(music_hoard(vec![])), matches(None));
// disconnected and so the receive within FetchState concludes there are no more matches.
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
matches.select().unwrap_browse();
}
}

View File

@ -1,40 +1,40 @@
mod browse;
mod critical;
mod error;
mod fetch;
mod info;
mod matches;
mod reload;
mod search;
mod browse_state;
mod critical_state;
mod error_state;
mod fetch_state;
mod info_state;
mod match_state;
mod reload_state;
mod search_state;
use std::sync::{Arc, Mutex};
use crate::tui::{
app::{selection::Selection, AppPublic, AppPublicInner, AppState, IAppAccess, IAppInteract},
app::{selection::Selection, AppPublic, AppPublicInner, AppState, IApp, IAppAccess},
event::EventSender,
lib::{interface::musicbrainz::IMusicBrainz, IMusicHoard},
};
use browse::AppBrowse;
use critical::AppCritical;
use error::AppError;
use fetch::AppFetch;
use info::AppInfo;
use matches::AppMatches;
use reload::AppReload;
use search::AppSearch;
use browse_state::BrowseState;
use critical_state::CriticalState;
use error_state::ErrorState;
use fetch_state::FetchState;
use info_state::InfoState;
use match_state::MatchState;
use reload_state::ReloadState;
use search_state::SearchState;
use super::IAppBase;
pub type App = AppState<
AppMachine<AppBrowse>,
AppMachine<AppInfo>,
AppMachine<AppReload>,
AppMachine<AppSearch>,
AppMachine<AppFetch>,
AppMachine<AppMatches>,
AppMachine<AppError>,
AppMachine<AppCritical>,
AppMachine<BrowseState>,
AppMachine<InfoState>,
AppMachine<ReloadState>,
AppMachine<SearchState>,
AppMachine<FetchState>,
AppMachine<MatchState>,
AppMachine<ErrorState>,
AppMachine<CriticalState>,
>;
pub struct AppMachine<STATE> {
@ -59,8 +59,8 @@ impl App {
let init_result = Self::init(&mut music_hoard);
let inner = AppInner::new(music_hoard, musicbrainz, events);
match init_result {
Ok(()) => AppMachine::browse(inner).into(),
Err(err) => AppMachine::critical(inner, err.to_string()).into(),
Ok(()) => AppMachine::browse_state(inner).into(),
Err(err) => AppMachine::critical_state(inner, err.to_string()).into(),
}
}
@ -71,40 +71,40 @@ impl App {
fn inner_ref(&self) -> &AppInner {
match self {
AppState::Browse(browse) => &browse.inner,
AppState::Info(info) => &info.inner,
AppState::Reload(reload) => &reload.inner,
AppState::Search(search) => &search.inner,
AppState::Fetch(fetch) => &fetch.inner,
AppState::Matches(matches) => &matches.inner,
AppState::Error(error) => &error.inner,
AppState::Critical(critical) => &critical.inner,
AppState::Browse(browse_state) => &browse_state.inner,
AppState::Info(info_state) => &info_state.inner,
AppState::Reload(reload_state) => &reload_state.inner,
AppState::Search(search_state) => &search_state.inner,
AppState::Fetch(fetch_state) => &fetch_state.inner,
AppState::Match(match_state) => &match_state.inner,
AppState::Error(error_state) => &error_state.inner,
AppState::Critical(critical_state) => &critical_state.inner,
}
}
fn inner_mut(&mut self) -> &mut AppInner {
match self {
AppState::Browse(browse) => &mut browse.inner,
AppState::Info(info) => &mut info.inner,
AppState::Reload(reload) => &mut reload.inner,
AppState::Search(search) => &mut search.inner,
AppState::Fetch(fetch) => &mut fetch.inner,
AppState::Matches(matches) => &mut matches.inner,
AppState::Error(error) => &mut error.inner,
AppState::Critical(critical) => &mut critical.inner,
AppState::Browse(browse_state) => &mut browse_state.inner,
AppState::Info(info_state) => &mut info_state.inner,
AppState::Reload(reload_state) => &mut reload_state.inner,
AppState::Search(search_state) => &mut search_state.inner,
AppState::Fetch(fetch_state) => &mut fetch_state.inner,
AppState::Match(match_state) => &mut match_state.inner,
AppState::Error(error_state) => &mut error_state.inner,
AppState::Critical(critical_state) => &mut critical_state.inner,
}
}
}
impl IAppInteract for App {
type BS = AppMachine<AppBrowse>;
type IS = AppMachine<AppInfo>;
type RS = AppMachine<AppReload>;
type SS = AppMachine<AppSearch>;
type FS = AppMachine<AppFetch>;
type MS = AppMachine<AppMatches>;
type ES = AppMachine<AppError>;
type CS = AppMachine<AppCritical>;
impl IApp for App {
type BrowseState = AppMachine<BrowseState>;
type InfoState = AppMachine<InfoState>;
type ReloadState = AppMachine<ReloadState>;
type SearchState = AppMachine<SearchState>;
type FetchState = AppMachine<FetchState>;
type MatchState = AppMachine<MatchState>;
type ErrorState = AppMachine<ErrorState>;
type CriticalState = AppMachine<CriticalState>;
fn is_running(&self) -> bool {
self.inner_ref().running
@ -117,8 +117,16 @@ impl IAppInteract for App {
fn state(
self,
) -> AppState<Self::BS, Self::IS, Self::RS, Self::SS, Self::FS, Self::MS, Self::ES, Self::CS>
{
) -> AppState<
Self::BrowseState,
Self::InfoState,
Self::ReloadState,
Self::SearchState,
Self::FetchState,
Self::MatchState,
Self::ErrorState,
Self::CriticalState,
> {
self
}
}
@ -139,7 +147,7 @@ impl IAppAccess for App {
AppState::Reload(reload) => reload.into(),
AppState::Search(search) => search.into(),
AppState::Fetch(fetch) => fetch.into(),
AppState::Matches(matches) => matches.into(),
AppState::Match(matches) => matches.into(),
AppState::Error(error) => error.into(),
AppState::Critical(critical) => critical.into(),
}
@ -179,7 +187,7 @@ mod tests {
use musichoard::collection::Collection;
use crate::tui::{
app::{AppState, IAppInteract, IAppInteractBrowse},
app::{AppState, IApp, IAppInteractBrowse},
lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard},
EventChannel,
};
@ -222,9 +230,9 @@ mod tests {
}
}
pub fn unwrap_matches(self) -> MS {
pub fn unwrap_match(self) -> MS {
match self {
AppState::Matches(matches) => matches,
AppState::Match(matches) => matches,
_ => panic!(),
}
}
@ -375,7 +383,7 @@ mod tests {
let (_, rx) = mpsc::channel();
let inner = app.unwrap_browse().inner;
let state = AppFetch::new(rx);
let state = FetchState::new(rx);
app = AppMachine { inner, state }.into();
let state = app.state();
@ -400,20 +408,21 @@ mod tests {
assert!(app.is_running());
let (_, rx) = mpsc::channel();
let fetch = AppFetch::new(rx);
app = AppMachine::matches(app.unwrap_browse().inner, AppMatches::new(None, fetch)).into();
let fetch = FetchState::new(rx);
app =
AppMachine::match_state(app.unwrap_browse().inner, MatchState::new(None, fetch)).into();
let state = app.state();
assert!(matches!(state, AppState::Matches(_)));
assert!(matches!(state, AppState::Match(_)));
app = state;
app = app.no_op();
let state = app.state();
assert!(matches!(state, AppState::Matches(_)));
assert!(matches!(state, AppState::Match(_)));
app = state;
let public = app.get();
assert!(matches!(public.state, AppState::Matches(_)));
assert!(matches!(public.state, AppState::Match(_)));
let app = app.force_quit();
assert!(!app.is_running());
@ -424,7 +433,7 @@ mod tests {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
assert!(app.is_running());
app = AppMachine::error(app.unwrap_browse().inner, "get rekt").into();
app = AppMachine::error_state(app.unwrap_browse().inner, "get rekt").into();
let state = app.state();
assert!(matches!(state, AppState::Error(_)));
@ -447,7 +456,7 @@ mod tests {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
assert!(app.is_running());
app = AppMachine::critical(app.unwrap_browse().inner, "get rekt").into();
app = AppMachine::critical_state(app.unwrap_browse().inner, "get rekt").into();
let state = app.state();
assert!(matches!(state, AppState::Critical(_)));

View File

@ -4,24 +4,24 @@ use crate::tui::app::{
AppPublic, AppState, IAppInteractReload,
};
pub struct AppReload;
pub struct ReloadState;
impl AppMachine<AppReload> {
pub fn reload(inner: AppInner) -> Self {
impl AppMachine<ReloadState> {
pub fn reload_state(inner: AppInner) -> Self {
AppMachine {
inner,
state: AppReload,
state: ReloadState,
}
}
}
impl From<AppMachine<AppReload>> for App {
fn from(machine: AppMachine<AppReload>) -> Self {
impl From<AppMachine<ReloadState>> for App {
fn from(machine: AppMachine<ReloadState>) -> Self {
AppState::Reload(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppReload>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppReload>) -> Self {
impl<'a> From<&'a mut AppMachine<ReloadState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<ReloadState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Reload(()),
@ -29,7 +29,7 @@ impl<'a> From<&'a mut AppMachine<AppReload>> for AppPublic<'a> {
}
}
impl IAppInteractReload for AppMachine<AppReload> {
impl IAppInteractReload for AppMachine<ReloadState> {
type APP = App;
fn reload_library(mut self) -> Self::APP {
@ -51,7 +51,7 @@ impl IAppInteractReload for AppMachine<AppReload> {
}
fn hide_reload_menu(self) -> Self::APP {
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
}
@ -59,16 +59,16 @@ trait IAppInteractReloadPrivate {
fn refresh(self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App;
}
impl IAppInteractReloadPrivate for AppMachine<AppReload> {
impl IAppInteractReloadPrivate for AppMachine<ReloadState> {
fn refresh(mut self, previous: KeySelection, result: Result<(), musichoard::Error>) -> App {
match result {
Ok(()) => {
self.inner
.selection
.select_by_id(self.inner.music_hoard.get_collection(), previous);
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
Err(err) => AppMachine::error(self.inner, err.to_string()).into(),
Err(err) => AppMachine::error_state(self.inner, err.to_string()).into(),
}
}
}
@ -81,7 +81,7 @@ mod tests {
#[test]
fn hide_reload_menu() {
let reload = AppMachine::reload(inner(music_hoard(vec![])));
let reload = AppMachine::reload_state(inner(music_hoard(vec![])));
let app = reload.hide_reload_menu();
app.unwrap_browse();
}
@ -95,7 +95,7 @@ mod tests {
.times(1)
.return_once(|| Ok(()));
let reload = AppMachine::reload(inner(music_hoard));
let reload = AppMachine::reload_state(inner(music_hoard));
let app = reload.reload_database();
app.unwrap_browse();
}
@ -109,7 +109,7 @@ mod tests {
.times(1)
.return_once(|| Ok(()));
let reload = AppMachine::reload(inner(music_hoard));
let reload = AppMachine::reload_state(inner(music_hoard));
let app = reload.reload_library();
app.unwrap_browse();
}
@ -123,7 +123,7 @@ mod tests {
.times(1)
.return_once(|| Err(musichoard::Error::DatabaseError(String::from("get rekt"))));
let reload = AppMachine::reload(inner(music_hoard));
let reload = AppMachine::reload_state(inner(music_hoard));
let app = reload.reload_database();
app.unwrap_error();
}

View File

@ -20,22 +20,22 @@ const REPLACE: [&str; 11] = ["-", "-", "-", "-", "-", "'", "'", "\"", "\"", "...
static AC: Lazy<AhoCorasick> =
Lazy::new(|| AhoCorasick::new(SPECIAL.map(|ch| ch.to_string())).unwrap());
pub struct AppSearch {
pub struct SearchState {
string: String,
orig: ListSelection,
memo: Vec<AppSearchMemo>,
memo: Vec<SearchStateMemo>,
}
struct AppSearchMemo {
struct SearchStateMemo {
index: Option<usize>,
char: bool,
}
impl AppMachine<AppSearch> {
pub fn search(inner: AppInner, orig: ListSelection) -> Self {
impl AppMachine<SearchState> {
pub fn search_state(inner: AppInner, orig: ListSelection) -> Self {
AppMachine {
inner,
state: AppSearch {
state: SearchState {
string: String::new(),
orig,
memo: vec![],
@ -44,14 +44,14 @@ impl AppMachine<AppSearch> {
}
}
impl From<AppMachine<AppSearch>> for App {
fn from(machine: AppMachine<AppSearch>) -> Self {
impl From<AppMachine<SearchState>> for App {
fn from(machine: AppMachine<SearchState>) -> Self {
AppState::Search(machine)
}
}
impl<'a> From<&'a mut AppMachine<AppSearch>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<AppSearch>) -> Self {
impl<'a> From<&'a mut AppMachine<SearchState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<SearchState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Search(&machine.state.string),
@ -59,13 +59,13 @@ impl<'a> From<&'a mut AppMachine<AppSearch>> for AppPublic<'a> {
}
}
impl IAppInteractSearch for AppMachine<AppSearch> {
impl IAppInteractSearch for AppMachine<SearchState> {
type APP = App;
fn append_character(mut self, ch: char) -> Self::APP {
self.state.string.push(ch);
let index = self.inner.selection.selected();
self.state.memo.push(AppSearchMemo { index, char: true });
self.state.memo.push(SearchStateMemo { index, char: true });
self.incremental_search(false);
self.into()
}
@ -73,7 +73,7 @@ impl IAppInteractSearch for AppMachine<AppSearch> {
fn search_next(mut self) -> Self::APP {
if !self.state.string.is_empty() {
let index = self.inner.selection.selected();
self.state.memo.push(AppSearchMemo { index, char: false });
self.state.memo.push(SearchStateMemo { index, char: false });
self.incremental_search(true);
}
self.into()
@ -91,12 +91,12 @@ impl IAppInteractSearch for AppMachine<AppSearch> {
}
fn finish_search(self) -> Self::APP {
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
fn cancel_search(mut self) -> Self::APP {
self.inner.selection.select_by_list(self.state.orig);
AppMachine::browse(self.inner).into()
AppMachine::browse_state(self.inner).into()
}
}
@ -120,7 +120,7 @@ trait IAppInteractSearchPrivate {
fn normalize_search(search: &str, lowercase: bool, asciify: bool) -> String;
}
impl IAppInteractSearchPrivate for AppMachine<AppSearch> {
impl IAppInteractSearchPrivate for AppMachine<SearchState> {
fn incremental_search(&mut self, next: bool) {
let collection = self.inner.music_hoard.get_collection();
let search = &self.state.string;
@ -254,7 +254,7 @@ mod tests {
#[test]
fn artist_incremental_search() {
// Empty collection.
let mut search = AppMachine::search(inner(music_hoard(vec![])), orig(None));
let mut search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None));
assert_eq!(search.inner.selection.selected(), None);
search.state.string = String::from("album_artist 'a'");
@ -263,7 +263,7 @@ mod tests {
// Basic test, first element.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("");
@ -280,7 +280,7 @@ mod tests {
// Basic test, non-first element.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("album_artist ");
@ -293,7 +293,7 @@ mod tests {
// Non-lowercase.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("Album_Artist ");
@ -306,7 +306,7 @@ mod tests {
// Non-ascii.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("album_artist ");
@ -319,7 +319,7 @@ mod tests {
// Non-lowercase, non-ascii.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("Album_Artist ");
@ -332,7 +332,7 @@ mod tests {
// Stop at name, not sort name.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("the ");
@ -345,7 +345,7 @@ mod tests {
// Search next with common prefix.
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
assert_eq!(search.inner.selection.selected(), Some(0));
search.state.string = String::from("album_artist");
@ -368,7 +368,7 @@ mod tests {
#[test]
fn album_incremental_search() {
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
search.inner.selection.increment_category();
assert_eq!(search.inner.selection.category(), Category::Album);
@ -392,7 +392,7 @@ mod tests {
#[test]
fn track_incremental_search() {
let mut search =
AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(1)));
search.inner.selection.increment_category();
search.inner.selection.increment_category();
assert_eq!(search.inner.selection.category(), Category::Track);
@ -418,7 +418,8 @@ mod tests {
#[test]
fn search() {
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
let search =
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
assert_eq!(search.inner.selection.selected(), Some(0));
let search = search.append_character('a').unwrap_search();
@ -458,7 +459,8 @@ mod tests {
#[test]
fn search_next_step_back() {
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
let search =
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
assert_eq!(search.inner.selection.selected(), Some(0));
let search = search.step_back().unwrap_search();
@ -495,7 +497,8 @@ mod tests {
#[test]
fn cancel_search() {
let search = AppMachine::search(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
let search =
AppMachine::search_state(inner(music_hoard(COLLECTION.to_owned())), orig(Some(2)));
assert_eq!(search.inner.selection.selected(), Some(0));
let search = search.append_character('a').unwrap_search();
@ -522,7 +525,7 @@ mod tests {
#[test]
fn empty_search() {
let search = AppMachine::search(inner(music_hoard(vec![])), orig(None));
let search = AppMachine::search_state(inner(music_hoard(vec![])), orig(None));
assert_eq!(search.inner.selection.selected(), None);
let search = search.append_character('a').unwrap_search();
@ -552,7 +555,7 @@ mod benches {
use super::*;
type Search = AppMachine<MockIMusicHoard, AppSearch>;
type Search = AppMachine<MockIMusicHoard, SearchState>;
#[bench]
fn is_char_sensitive(b: &mut Bencher) {

View File

@ -8,26 +8,37 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection};
use crate::tui::lib::interface::musicbrainz::Match;
pub enum AppState<BS, IS, RS, SS, FS, MS, ES, CS> {
Browse(BS),
Info(IS),
Reload(RS),
Search(SS),
Fetch(FS),
Matches(MS),
Error(ES),
Critical(CS),
pub enum AppState<
BrowseState,
InfoState,
ReloadState,
SearchState,
FetchState,
MatchState,
ErrorState,
CriticalState,
> {
Browse(BrowseState),
Info(InfoState),
Reload(ReloadState),
Search(SearchState),
Fetch(FetchState),
Match(MatchState),
Error(ErrorState),
Critical(CriticalState),
}
pub trait IAppInteract {
type BS: IAppBase<APP = Self> + IAppInteractBrowse<APP = Self>;
type IS: IAppBase<APP = Self> + IAppInteractInfo<APP = Self>;
type RS: IAppBase<APP = Self> + IAppInteractReload<APP = Self>;
type SS: IAppBase<APP = Self> + IAppInteractSearch<APP = Self>;
type FS: IAppBase<APP = Self> + IAppInteractFetch<APP = Self> + IAppEventFetch<APP = Self>;
type MS: IAppBase<APP = Self> + IAppInteractMatches<APP = Self>;
type ES: IAppBase<APP = Self> + IAppInteractError<APP = Self>;
type CS: IAppBase<APP = Self>;
pub trait IApp {
type BrowseState: IAppBase<APP = Self> + IAppInteractBrowse<APP = Self>;
type InfoState: IAppBase<APP = Self> + IAppInteractInfo<APP = Self>;
type ReloadState: IAppBase<APP = Self> + IAppInteractReload<APP = Self>;
type SearchState: IAppBase<APP = Self> + IAppInteractSearch<APP = Self>;
type FetchState: IAppBase<APP = Self>
+ IAppInteractFetch<APP = Self>
+ IAppEventFetch<APP = Self>;
type MatchState: IAppBase<APP = Self> + IAppInteractMatch<APP = Self>;
type ErrorState: IAppBase<APP = Self> + IAppInteractError<APP = Self>;
type CriticalState: IAppBase<APP = Self>;
fn is_running(&self) -> bool;
fn force_quit(self) -> Self;
@ -35,17 +46,26 @@ pub trait IAppInteract {
#[allow(clippy::type_complexity)]
fn state(
self,
) -> AppState<Self::BS, Self::IS, Self::RS, Self::SS, Self::FS, Self::MS, Self::ES, Self::CS>;
) -> AppState<
Self::BrowseState,
Self::InfoState,
Self::ReloadState,
Self::SearchState,
Self::FetchState,
Self::MatchState,
Self::ErrorState,
Self::CriticalState,
>;
}
pub trait IAppBase {
type APP: IAppInteract;
type APP: IApp;
fn no_op(self) -> Self::APP;
}
pub trait IAppInteractBrowse {
type APP: IAppInteract;
type APP: IApp;
fn quit(self) -> Self::APP;
@ -64,13 +84,13 @@ pub trait IAppInteractBrowse {
}
pub trait IAppInteractInfo {
type APP: IAppInteract;
type APP: IApp;
fn hide_info_overlay(self) -> Self::APP;
}
pub trait IAppInteractReload {
type APP: IAppInteract;
type APP: IApp;
fn reload_library(self) -> Self::APP;
fn reload_database(self) -> Self::APP;
@ -78,7 +98,7 @@ pub trait IAppInteractReload {
}
pub trait IAppInteractSearch {
type APP: IAppInteract;
type APP: IApp;
fn append_character(self, ch: char) -> Self::APP;
fn search_next(self) -> Self::APP;
@ -88,19 +108,19 @@ pub trait IAppInteractSearch {
}
pub trait IAppInteractFetch {
type APP: IAppInteract;
type APP: IApp;
fn abort(self) -> Self::APP;
}
pub trait IAppEventFetch {
type APP: IAppInteract;
type APP: IApp;
fn fetch_result_ready(self) -> Self::APP;
}
pub trait IAppInteractMatches {
type APP: IAppInteract;
pub trait IAppInteractMatch {
type APP: IApp;
fn prev_match(self) -> Self::APP;
fn next_match(self) -> Self::APP;
@ -110,7 +130,7 @@ pub trait IAppInteractMatches {
}
pub trait IAppInteractError {
type APP: IAppInteract;
type APP: IApp;
fn dismiss_error(self) -> Self::APP;
}
@ -146,42 +166,42 @@ impl<T> From<Match<T>> for MatchOption<T> {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppArtistMatches {
pub struct ArtistMatches {
pub matching: ArtistMeta,
pub list: Vec<MatchOption<ArtistMeta>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AppAlbumMatches {
pub struct AlbumMatches {
pub matching: AlbumMeta,
pub list: Vec<MatchOption<AlbumMeta>>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AppMatchesInfo {
Artist(AppArtistMatches),
Album(AppAlbumMatches),
pub enum MatchStateInfo {
Artist(ArtistMatches),
Album(AlbumMatches),
}
impl AppMatchesInfo {
impl MatchStateInfo {
pub fn artist<M: Into<MatchOption<ArtistMeta>>>(matching: ArtistMeta, list: Vec<M>) -> Self {
let list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
AppMatchesInfo::Artist(AppArtistMatches { matching, list })
MatchStateInfo::Artist(ArtistMatches { matching, list })
}
pub fn album<M: Into<MatchOption<AlbumMeta>>>(matching: AlbumMeta, list: Vec<M>) -> Self {
let list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
AppMatchesInfo::Album(AppAlbumMatches { matching, list })
MatchStateInfo::Album(AlbumMatches { matching, list })
}
}
pub struct AppPublicMatches<'app> {
pub matches: Option<&'app AppMatchesInfo>,
pub struct MatchStatePublic<'app> {
pub info: Option<&'app MatchStateInfo>,
pub state: &'app mut WidgetState,
}
pub type AppPublicState<'app> =
AppState<(), (), (), &'app str, (), AppPublicMatches<'app>, &'app str, &'app str>;
AppState<(), (), (), &'app str, (), MatchStatePublic<'app>, &'app str, &'app str>;
impl<BS, IS, RS, SS, FS, MS, ES, CS> AppState<BS, IS, RS, SS, FS, MS, ES, CS> {
pub fn is_search(&self) -> bool {

View File

@ -5,8 +5,8 @@ use mockall::automock;
use crate::tui::{
app::{
AppState, Delta, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractFetch,
IAppInteractInfo, IAppInteractMatches, IAppInteractReload, IAppInteractSearch,
AppState, Delta, IApp, IAppInteractBrowse, IAppInteractError, IAppInteractFetch,
IAppInteractInfo, IAppInteractMatch, IAppInteractReload, IAppInteractSearch,
},
event::{Event, EventError, EventReceiver},
};
@ -14,20 +14,20 @@ use crate::tui::{
use super::app::{IAppBase, IAppEventFetch};
#[cfg_attr(test, automock)]
pub trait IEventHandler<APP: IAppInteract> {
pub trait IEventHandler<APP: IApp> {
fn handle_next_event(&self, app: APP) -> Result<APP, EventError>;
}
trait IEventHandlerPrivate<APP: IAppInteract> {
trait IEventHandlerPrivate<APP: IApp> {
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP;
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP;
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP;
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP;
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP;
fn handle_fetch_key_event(app: <APP as IAppInteract>::FS, key_event: KeyEvent) -> APP;
fn handle_matches_key_event(app: <APP as IAppInteract>::MS, key_event: KeyEvent) -> APP;
fn handle_error_key_event(app: <APP as IAppInteract>::ES, key_event: KeyEvent) -> APP;
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, key_event: KeyEvent) -> APP;
fn handle_browse_key_event(app: <APP as IApp>::BrowseState, key_event: KeyEvent) -> APP;
fn handle_info_key_event(app: <APP as IApp>::InfoState, key_event: KeyEvent) -> APP;
fn handle_reload_key_event(app: <APP as IApp>::ReloadState, key_event: KeyEvent) -> APP;
fn handle_search_key_event(app: <APP as IApp>::SearchState, key_event: KeyEvent) -> APP;
fn handle_fetch_key_event(app: <APP as IApp>::FetchState, key_event: KeyEvent) -> APP;
fn handle_match_key_event(app: <APP as IApp>::MatchState, key_event: KeyEvent) -> APP;
fn handle_error_key_event(app: <APP as IApp>::ErrorState, key_event: KeyEvent) -> APP;
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, key_event: KeyEvent) -> APP;
fn handle_fetch_result_ready_event(app: APP) -> APP;
}
@ -43,7 +43,7 @@ impl EventHandler {
}
}
impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
impl<APP: IApp> IEventHandler<APP> for EventHandler {
fn handle_next_event(&self, app: APP) -> Result<APP, EventError> {
Ok(match self.events.recv()? {
Event::Key(key_event) => Self::handle_key_event(app, key_event),
@ -52,7 +52,7 @@ impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
}
}
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
fn handle_key_event(app: APP, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL {
match key_event.code {
@ -63,47 +63,39 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
match app.state() {
AppState::Browse(browse) => {
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event)
AppState::Browse(browse_state) => {
Self::handle_browse_key_event(browse_state, key_event)
}
AppState::Info(info) => {
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event)
AppState::Info(info_state) => Self::handle_info_key_event(info_state, key_event),
AppState::Reload(reload_state) => {
Self::handle_reload_key_event(reload_state, key_event)
}
AppState::Reload(reload) => {
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event)
AppState::Search(search_state) => {
Self::handle_search_key_event(search_state, key_event)
}
AppState::Search(search) => {
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, key_event)
}
AppState::Fetch(fetch) => {
<Self as IEventHandlerPrivate<APP>>::handle_fetch_key_event(fetch, key_event)
}
AppState::Matches(matches) => {
<Self as IEventHandlerPrivate<APP>>::handle_matches_key_event(matches, key_event)
}
AppState::Error(error) => {
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event)
}
AppState::Critical(critical) => {
<Self as IEventHandlerPrivate<APP>>::handle_critical_key_event(critical, key_event)
AppState::Fetch(fetch_state) => Self::handle_fetch_key_event(fetch_state, key_event),
AppState::Match(match_state) => Self::handle_match_key_event(match_state, key_event),
AppState::Error(error_state) => Self::handle_error_key_event(error_state, key_event),
AppState::Critical(critical_state) => {
Self::handle_critical_key_event(critical_state, key_event)
}
}
}
fn handle_fetch_result_ready_event(app: APP) -> APP {
match app.state() {
AppState::Browse(browse) => browse.no_op(),
AppState::Info(info) => info.no_op(),
AppState::Reload(reload) => reload.no_op(),
AppState::Search(search) => search.no_op(),
AppState::Fetch(fetch) => fetch.fetch_result_ready(),
AppState::Matches(matches) => matches.no_op(),
AppState::Error(error) => error.no_op(),
AppState::Critical(critical) => critical.no_op(),
AppState::Browse(browse_state) => browse_state.no_op(),
AppState::Info(info_state) => info_state.no_op(),
AppState::Reload(reload_state) => reload_state.no_op(),
AppState::Search(search_state) => search_state.no_op(),
AppState::Fetch(fetch_state) => fetch_state.fetch_result_ready(),
AppState::Match(match_state) => match_state.no_op(),
AppState::Error(error_state) => error_state.no_op(),
AppState::Critical(critical_state) => critical_state.no_op(),
}
}
fn handle_browse_key_event(app: <APP as IAppInteract>::BS, key_event: KeyEvent) -> APP {
fn handle_browse_key_event(app: <APP as IApp>::BrowseState, key_event: KeyEvent) -> APP {
match key_event.code {
// Exit application on `ESC` or `q`.
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.quit(),
@ -133,7 +125,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
}
fn handle_info_key_event(app: <APP as IAppInteract>::IS, key_event: KeyEvent) -> APP {
fn handle_info_key_event(app: <APP as IApp>::InfoState, key_event: KeyEvent) -> APP {
match key_event.code {
// Toggle overlay.
KeyCode::Esc
@ -146,7 +138,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
}
fn handle_reload_key_event(app: <APP as IAppInteract>::RS, key_event: KeyEvent) -> APP {
fn handle_reload_key_event(app: <APP as IApp>::ReloadState, key_event: KeyEvent) -> APP {
match key_event.code {
// Reload keys.
KeyCode::Char('l') | KeyCode::Char('L') => app.reload_library(),
@ -162,7 +154,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
}
fn handle_search_key_event(app: <APP as IAppInteract>::SS, key_event: KeyEvent) -> APP {
fn handle_search_key_event(app: <APP as IApp>::SearchState, key_event: KeyEvent) -> APP {
if key_event.modifiers == KeyModifiers::CONTROL {
return match key_event.code {
KeyCode::Char('s') | KeyCode::Char('S') => app.search_next(),
@ -182,7 +174,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
}
fn handle_fetch_key_event(app: <APP as IAppInteract>::FS, key_event: KeyEvent) -> APP {
fn handle_fetch_key_event(app: <APP as IApp>::FetchState, key_event: KeyEvent) -> APP {
match key_event.code {
// Abort.
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
@ -191,7 +183,7 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
}
fn handle_matches_key_event(app: <APP as IAppInteract>::MS, key_event: KeyEvent) -> APP {
fn handle_match_key_event(app: <APP as IApp>::MatchState, key_event: KeyEvent) -> APP {
match key_event.code {
// Abort.
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
@ -204,12 +196,12 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
}
}
fn handle_error_key_event(app: <APP as IAppInteract>::ES, _key_event: KeyEvent) -> APP {
fn handle_error_key_event(app: <APP as IApp>::ErrorState, _key_event: KeyEvent) -> APP {
// Any key dismisses the error.
app.dismiss_error()
}
fn handle_critical_key_event(app: <APP as IAppInteract>::CS, _key_event: KeyEvent) -> APP {
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, _key_event: KeyEvent) -> APP {
// No action is allowed.
app.no_op()
}

View File

@ -20,7 +20,7 @@ use ratatui::{backend::Backend, Terminal};
use std::{io, marker::PhantomData};
use crate::tui::{
app::{IAppAccess, IAppInteract},
app::{IApp, IAppAccess},
event::EventError,
handler::IEventHandler,
listener::IEventListener,
@ -46,12 +46,12 @@ impl From<EventError> for Error {
}
}
pub struct Tui<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> {
pub struct Tui<B: Backend, UI: IUi, APP: IApp + IAppAccess> {
terminal: Terminal<B>,
_phantom: PhantomData<(UI, APP)>,
}
impl<B: Backend, UI: IUi, APP: IAppInteract + IAppAccess> Tui<B, UI, APP> {
impl<B: Backend, UI: IUi, APP: IApp + IAppAccess> Tui<B, UI, APP> {
fn init(&mut self) -> Result<(), Error> {
self.terminal.hide_cursor()?;
self.terminal.clear()?;

View File

@ -4,7 +4,7 @@ use musichoard::collection::{
track::{TrackFormat, TrackQuality},
};
use crate::tui::app::{AppMatchesInfo, MatchOption};
use crate::tui::app::{MatchOption, MatchStateInfo};
pub struct UiDisplay;
@ -114,11 +114,11 @@ impl UiDisplay {
"Matching nothing"
}
pub fn display_matching_info(matches: Option<&AppMatchesInfo>) -> String {
match matches.as_ref() {
pub fn display_matching_info(info: Option<&MatchStateInfo>) -> String {
match info.as_ref() {
Some(kind) => match kind {
AppMatchesInfo::Artist(m) => UiDisplay::display_artist_matching(&m.matching),
AppMatchesInfo::Album(m) => UiDisplay::display_album_matching(&m.matching),
MatchStateInfo::Artist(m) => UiDisplay::display_artist_matching(&m.matching),
MatchStateInfo::Album(m) => UiDisplay::display_album_matching(&m.matching),
},
None => UiDisplay::display_nothing_matching().to_string(),
}

View File

@ -2,29 +2,29 @@ use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta};
use ratatui::widgets::{List, ListItem};
use crate::tui::{
app::{AppMatchesInfo, MatchOption, WidgetState},
app::{MatchOption, MatchStateInfo, WidgetState},
ui::display::UiDisplay,
};
pub struct MatchesState<'a, 'b> {
pub struct MatchOverlay<'a, 'b> {
pub matching: String,
pub list: List<'a>,
pub state: &'b mut WidgetState,
}
impl<'a, 'b> MatchesState<'a, 'b> {
pub fn new(matches: Option<&AppMatchesInfo>, state: &'b mut WidgetState) -> Self {
match matches {
impl<'a, 'b> MatchOverlay<'a, 'b> {
pub fn new(info: Option<&MatchStateInfo>, state: &'b mut WidgetState) -> Self {
match info {
Some(info) => match info {
AppMatchesInfo::Artist(m) => Self::artists(&m.matching, &m.list, state),
AppMatchesInfo::Album(m) => Self::albums(&m.matching, &m.list, state),
MatchStateInfo::Artist(m) => Self::artists(&m.matching, &m.list, state),
MatchStateInfo::Album(m) => Self::albums(&m.matching, &m.list, state),
},
None => Self::empty(state),
}
}
fn empty(state: &'b mut WidgetState) -> Self {
MatchesState {
MatchOverlay {
matching: UiDisplay::display_nothing_matching().to_string(),
list: List::default(),
state,
@ -46,7 +46,7 @@ impl<'a, 'b> MatchesState<'a, 'b> {
.collect::<Vec<ListItem>>(),
);
MatchesState {
MatchOverlay {
matching,
list,
state,
@ -68,7 +68,7 @@ impl<'a, 'b> MatchesState<'a, 'b> {
.collect::<Vec<ListItem>>(),
);
MatchesState {
MatchOverlay {
matching,
list,
state,

View File

@ -60,9 +60,9 @@ impl Minibuffer<'_> {
paragraphs: vec![Paragraph::new("fetching..."), Paragraph::new("q: abort")],
columns: 2,
},
AppState::Matches(public) => Minibuffer {
AppState::Match(public) => Minibuffer {
paragraphs: vec![
Paragraph::new(UiDisplay::display_matching_info(public.matches)),
Paragraph::new(UiDisplay::display_matching_info(public.info)),
Paragraph::new("q: abort"),
],
columns: 2,

View File

@ -1,33 +1,33 @@
mod browse;
mod browse_state;
mod display;
mod error;
mod fetch;
mod info;
mod matches;
mod error_state;
mod fetch_state;
mod info_state;
mod match_state;
mod minibuffer;
mod overlay;
mod reload;
mod reload_state;
mod style;
mod widgets;
use fetch::FetchOverlay;
use ratatui::{layout::Rect, widgets::Paragraph, Frame};
use musichoard::collection::{album::Album, Collection};
use crate::tui::{
app::{AppMatchesInfo, AppPublicState, AppState, Category, IAppAccess, Selection, WidgetState},
app::{AppPublicState, AppState, Category, IAppAccess, MatchStateInfo, Selection, WidgetState},
ui::{
browse::{
browse_state::{
AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState,
},
display::UiDisplay,
error::ErrorOverlay,
info::{AlbumOverlay, ArtistOverlay},
matches::MatchesState,
error_state::ErrorOverlay,
fetch_state::FetchOverlay,
info_state::{AlbumOverlay, ArtistOverlay},
match_state::MatchOverlay,
minibuffer::Minibuffer,
overlay::{OverlayBuilder, OverlaySize},
reload::ReloadOverlay,
reload_state::ReloadOverlay,
widgets::UiWidget,
},
};
@ -137,13 +137,13 @@ impl Ui {
UiWidget::render_overlay_widget("Fetching", fetch_text, area, false, frame)
}
fn render_matches_overlay(
matches: Option<&AppMatchesInfo>,
fn render_match_overlay(
info: Option<&MatchStateInfo>,
state: &mut WidgetState,
frame: &mut Frame,
) {
let area = OverlayBuilder::default().build(frame.size());
let st = MatchesState::new(matches, state);
let st = MatchOverlay::new(info, state);
UiWidget::render_overlay_list_widget(&st.matching, st.list, st.state, true, area, frame)
}
@ -169,9 +169,7 @@ impl IUi for Ui {
AppState::Info(()) => Self::render_info_overlay(collection, selection, frame),
AppState::Reload(()) => Self::render_reload_overlay(frame),
AppState::Fetch(()) => Self::render_fetch_overlay(frame),
AppState::Matches(public) => {
Self::render_matches_overlay(public.matches, public.state, frame)
}
AppState::Match(public) => Self::render_match_overlay(public.info, public.state, frame),
AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame),
AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame),
_ => {}
@ -187,7 +185,7 @@ mod tests {
};
use crate::tui::{
app::{AppPublic, AppPublicInner, AppPublicMatches, Delta, MatchOption},
app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic},
lib::interface::musicbrainz::Match,
testmod::COLLECTION,
tests::terminal,
@ -209,8 +207,8 @@ mod tests {
AppState::Reload(()) => AppState::Reload(()),
AppState::Search(s) => AppState::Search(s),
AppState::Fetch(()) => AppState::Fetch(()),
AppState::Matches(ref mut m) => AppState::Matches(AppPublicMatches {
matches: m.matches,
AppState::Match(ref mut m) => AppState::Match(MatchStatePublic {
info: m.info,
state: m.state,
}),
AppState::Error(s) => AppState::Error(s),
@ -230,16 +228,16 @@ mod tests {
}
}
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> AppMatchesInfo {
fn artist_matches(matching: ArtistMeta, list: Vec<Match<ArtistMeta>>) -> MatchStateInfo {
let mut list: Vec<MatchOption<ArtistMeta>> = list.into_iter().map(Into::into).collect();
list.push(MatchOption::CannotHaveMbid);
AppMatchesInfo::artist(matching, list)
MatchStateInfo::artist(matching, list)
}
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> AppMatchesInfo {
fn album_matches(matching: AlbumMeta, list: Vec<Match<AlbumMeta>>) -> MatchStateInfo {
let mut list: Vec<MatchOption<AlbumMeta>> = list.into_iter().map(Into::into).collect();
list.push(MatchOption::CannotHaveMbid);
AppMatchesInfo::album(matching, list)
MatchStateInfo::album(matching, list)
}
fn draw_test_suite(collection: &Collection, selection: &mut Selection) {
@ -322,8 +320,8 @@ mod tests {
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Matches(AppPublicMatches {
matches: None,
state: AppState::Match(MatchStatePublic {
info: None,
state: &mut widget_state,
}),
};
@ -351,8 +349,8 @@ mod tests {
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Matches(AppPublicMatches {
matches: Some(&artist_matches),
state: AppState::Match(MatchStatePublic {
info: Some(&artist_matches),
state: &mut widget_state,
}),
};
@ -385,8 +383,8 @@ mod tests {
let mut app = AppPublic {
inner: public_inner(collection, &mut selection),
state: AppState::Matches(AppPublicMatches {
matches: Some(&album_matches),
state: AppState::Match(MatchStatePublic {
info: Some(&album_matches),
state: &mut widget_state,
}),
};