Provide search functionality through the TUI #134

Merged
wojtek merged 35 commits from 24---provide-search-functionality-through-the-tui into main 2024-02-18 22:12:42 +01:00
2 changed files with 73 additions and 57 deletions
Showing only changes of commit 9cf29b99b8 - Show all commits

View File

@ -3,7 +3,7 @@
use musichoard::collection::Collection; use musichoard::collection::Collection;
use crate::tui::{ use crate::tui::{
app::selection::{ActiveSelection, Delta, Selection}, app::selection::{Delta, IdSelection, Selection, ListSelection},
lib::IMusicHoard, lib::IMusicHoard,
}; };
@ -143,7 +143,7 @@ pub struct App<MH: IMusicHoard> {
state: AppState<(), (), (), String, String, String>, state: AppState<(), (), (), String, String, String>,
// FIXME: is it possible to use a wrapper struct? - when state() is called return a wrapper // FIXME: is it possible to use a wrapper struct? - when state() is called return a wrapper
// around App<MH> which will contain App. // around App<MH> which will contain App.
orig: Option<usize>, orig: Option<ListSelection>,
memo: Vec<(Option<usize>, bool)>, memo: Vec<(Option<usize>, bool)>,
} }
@ -250,8 +250,7 @@ impl<MH: IMusicHoard> IAppInteractBrowse for App<MH> {
fn begin_search(&mut self) { fn begin_search(&mut self) {
assert!(self.state.is_browse()); assert!(self.state.is_browse());
self.state = AppState::Search(String::new()); self.state = AppState::Search(String::new());
// FIXME: this should be the entire selection - not just the artist. self.orig = Some(ListSelection::get(&self.selection));
self.orig = self.selection.selected_artist();
self.memo = vec![]; self.memo = vec![];
self.selection self.selection
.reset_artist(self.music_hoard.get_collection()); .reset_artist(self.music_hoard.get_collection());
@ -267,13 +266,13 @@ impl<MH: IMusicHoard> IAppInteractInfo for App<MH> {
impl<MH: IMusicHoard> IAppInteractReload for App<MH> { impl<MH: IMusicHoard> IAppInteractReload for App<MH> {
fn reload_library(&mut self) { fn reload_library(&mut self) {
let previous = ActiveSelection::get(self.music_hoard.get_collection(), &self.selection); let previous = IdSelection::get(self.music_hoard.get_collection(), &self.selection);
let result = self.music_hoard.rescan_library(); let result = self.music_hoard.rescan_library();
self.refresh(previous, result); self.refresh(previous, result);
} }
fn reload_database(&mut self) { fn reload_database(&mut self) {
let previous = ActiveSelection::get(self.music_hoard.get_collection(), &self.selection); let previous = IdSelection::get(self.music_hoard.get_collection(), &self.selection);
let result = self.music_hoard.load_from_database(); let result = self.music_hoard.load_from_database();
self.refresh(previous, result); self.refresh(previous, result);
} }
@ -285,11 +284,11 @@ impl<MH: IMusicHoard> IAppInteractReload for App<MH> {
} }
trait IAppInteractReloadPrivate { trait IAppInteractReloadPrivate {
fn refresh(&mut self, previous: ActiveSelection, result: Result<(), musichoard::Error>); fn refresh(&mut self, previous: IdSelection, result: Result<(), musichoard::Error>);
} }
impl<MH: IMusicHoard> IAppInteractReloadPrivate for App<MH> { impl<MH: IMusicHoard> IAppInteractReloadPrivate for App<MH> {
fn refresh(&mut self, previous: ActiveSelection, result: Result<(), musichoard::Error>) { fn refresh(&mut self, previous: IdSelection, result: Result<(), musichoard::Error>) {
assert!(self.state.is_reload()); assert!(self.state.is_reload());
match result { match result {
Ok(()) => { Ok(()) => {
@ -342,8 +341,7 @@ impl<MH: IMusicHoard> IAppInteractSearch for App<MH> {
fn cancel_search(&mut self) { fn cancel_search(&mut self) {
assert!(self.state.is_search()); assert!(self.state.is_search());
let collection = self.music_hoard.get_collection(); self.selection.select_by_list(self.orig.take().unwrap());
self.selection.select_artist(collection, self.orig);
self.state = AppState::Browse(()); self.state = AppState::Browse(());
} }
} }

View File

@ -70,12 +70,14 @@ impl Selection {
} }
} }
pub fn select_by_id(&mut self, artists: &[Artist], selected: ActiveSelection) { pub fn select_by_list(&mut self, selected: ListSelection) {
self.artist.reinitialise(artists, selected.artist); self.artist.state.list = selected.artist;
self.artist.album.state.list = selected.album;
self.artist.album.track.state.list = selected.track;
} }
pub fn selected_artist(&mut self) -> Option<usize> { pub fn select_by_id(&mut self, artists: &[Artist], selected: IdSelection) {
self.artist.state.list.selected() self.artist.reinitialise(artists, selected.artist);
} }
pub fn select_artist(&mut self, artists: &[Artist], index: Option<usize>) { pub fn select_artist(&mut self, artists: &[Artist], index: Option<usize>) {
@ -84,7 +86,7 @@ impl Selection {
pub fn reset_artist(&mut self, artists: &[Artist]) { pub fn reset_artist(&mut self, artists: &[Artist]) {
if self.artist.state.list.selected() != Some(0) { if self.artist.state.list.selected() != Some(0) {
self.select_by_id(artists, ActiveSelection { artist: None }); self.select_by_id(artists, IdSelection { artist: None });
} }
} }
@ -165,7 +167,7 @@ impl ArtistSelection {
selection selection
} }
fn reinitialise(&mut self, artists: &[Artist], active: Option<ActiveArtist>) { fn reinitialise(&mut self, artists: &[Artist], active: Option<IdSelectArtist>) {
if let Some(active) = active { if let Some(active) = active {
let result = artists.binary_search_by(|a| a.get_sort_key().cmp(&active.artist_id)); let result = artists.binary_search_by(|a| a.get_sort_key().cmp(&active.artist_id));
match result { match result {
@ -181,7 +183,7 @@ impl ArtistSelection {
&mut self, &mut self,
artists: &[Artist], artists: &[Artist],
index: usize, index: usize,
active_album: Option<ActiveAlbum>, active_album: Option<IdSelectAlbum>,
) { ) {
if artists.is_empty() { if artists.is_empty() {
self.state.list.select(None); self.state.list.select(None);
@ -359,7 +361,7 @@ impl AlbumSelection {
selection selection
} }
fn reinitialise(&mut self, albums: &[Album], album: Option<ActiveAlbum>) { fn reinitialise(&mut self, albums: &[Album], album: Option<IdSelectAlbum>) {
if let Some(album) = album { if let Some(album) = album {
let result = albums.binary_search_by(|a| a.get_sort_key().cmp(&album.album_id)); let result = albums.binary_search_by(|a| a.get_sort_key().cmp(&album.album_id));
match result { match result {
@ -375,7 +377,7 @@ impl AlbumSelection {
&mut self, &mut self,
albums: &[Album], albums: &[Album],
index: usize, index: usize,
active_track: Option<ActiveTrack>, active_track: Option<IdSelectTrack>,
) { ) {
if albums.is_empty() { if albums.is_empty() {
self.state.list.select(None); self.state.list.select(None);
@ -443,7 +445,7 @@ impl TrackSelection {
selection selection
} }
fn reinitialise(&mut self, tracks: &[Track], track: Option<ActiveTrack>) { fn reinitialise(&mut self, tracks: &[Track], track: Option<IdSelectTrack>) {
if let Some(track) = track { if let Some(track) = track {
let result = tracks.binary_search_by(|t| t.get_sort_key().cmp(&track.track_id)); let result = tracks.binary_search_by(|t| t.get_sort_key().cmp(&track.track_id));
match result { match result {
@ -494,61 +496,77 @@ impl TrackSelection {
} }
} }
pub struct ActiveSelection { pub struct ListSelection {
artist: Option<ActiveArtist>, artist: ListState,
album: ListState,
track: ListState,
} }
struct ActiveArtist { impl ListSelection {
artist_id: ArtistId, pub fn get(selection: &Selection) -> Self {
album: Option<ActiveAlbum>, ListSelection {
} artist: selection.artist.state.list.clone(),
album: selection.artist.album.state.list.clone(),
struct ActiveAlbum { track: selection.artist.album.track.state.list.clone(),
album_id: AlbumId,
track: Option<ActiveTrack>,
}
struct ActiveTrack {
track_id: TrackId,
}
impl ActiveSelection {
pub fn get(collection: &Collection, selection: &Selection) -> Self {
ActiveSelection {
artist: ActiveArtist::get(collection, &selection.artist),
} }
} }
} }
impl ActiveArtist { pub struct IdSelection {
artist: Option<IdSelectArtist>,
}
struct IdSelectArtist {
artist_id: ArtistId,
album: Option<IdSelectAlbum>,
}
struct IdSelectAlbum {
album_id: AlbumId,
track: Option<IdSelectTrack>,
}
struct IdSelectTrack {
track_id: TrackId,
}
impl IdSelection {
pub fn get(collection: &Collection, selection: &Selection) -> Self {
IdSelection {
artist: IdSelectArtist::get(collection, &selection.artist),
}
}
}
impl IdSelectArtist {
fn get(artists: &[Artist], selection: &ArtistSelection) -> Option<Self> { fn get(artists: &[Artist], selection: &ArtistSelection) -> Option<Self> {
selection.state.list.selected().map(|index| { selection.state.list.selected().map(|index| {
let artist = &artists[index]; let artist = &artists[index];
ActiveArtist { IdSelectArtist {
artist_id: artist.get_sort_key().clone(), artist_id: artist.get_sort_key().clone(),
album: ActiveAlbum::get(&artist.albums, &selection.album), album: IdSelectAlbum::get(&artist.albums, &selection.album),
} }
}) })
} }
} }
impl ActiveAlbum { impl IdSelectAlbum {
fn get(albums: &[Album], selection: &AlbumSelection) -> Option<Self> { fn get(albums: &[Album], selection: &AlbumSelection) -> Option<Self> {
selection.state.list.selected().map(|index| { selection.state.list.selected().map(|index| {
let album = &albums[index]; let album = &albums[index];
ActiveAlbum { IdSelectAlbum {
album_id: album.get_sort_key().clone(), album_id: album.get_sort_key().clone(),
track: ActiveTrack::get(&album.tracks, &selection.track), track: IdSelectTrack::get(&album.tracks, &selection.track),
} }
}) })
} }
} }
impl ActiveTrack { impl IdSelectTrack {
fn get(tracks: &[Track], selection: &TrackSelection) -> Option<Self> { fn get(tracks: &[Track], selection: &TrackSelection) -> Option<Self> {
selection.state.list.selected().map(|index| { selection.state.list.selected().map(|index| {
let track = &tracks[index]; let track = &tracks[index];
ActiveTrack { IdSelectTrack {
track_id: track.get_sort_key().clone(), track_id: track.get_sort_key().clone(),
} }
}) })
@ -626,20 +644,20 @@ mod tests {
// Re-initialise. // Re-initialise.
let expected = sel.clone(); let expected = sel.clone();
let active_track = ActiveTrack::get(tracks, &sel); let active_track = IdSelectTrack::get(tracks, &sel);
sel.reinitialise(tracks, active_track); sel.reinitialise(tracks, active_track);
assert_eq!(sel, expected); assert_eq!(sel, expected);
// Re-initialise out-of-bounds. // Re-initialise out-of-bounds.
let mut expected = sel.clone(); let mut expected = sel.clone();
expected.decrement(tracks, Delta::Line); expected.decrement(tracks, Delta::Line);
let active_track = ActiveTrack::get(tracks, &sel); let active_track = IdSelectTrack::get(tracks, &sel);
sel.reinitialise(&tracks[..(tracks.len() - 1)], active_track); sel.reinitialise(&tracks[..(tracks.len() - 1)], active_track);
assert_eq!(sel, expected); assert_eq!(sel, expected);
// Re-initialise empty. // Re-initialise empty.
let expected = TrackSelection::initialise(&[]); let expected = TrackSelection::initialise(&[]);
let active_track = ActiveTrack::get(tracks, &sel); let active_track = IdSelectTrack::get(tracks, &sel);
sel.reinitialise(&[], active_track); sel.reinitialise(&[], active_track);
assert_eq!(sel, expected); assert_eq!(sel, expected);
} }
@ -748,20 +766,20 @@ mod tests {
// Re-initialise. // Re-initialise.
let expected = sel.clone(); let expected = sel.clone();
let active_album = ActiveAlbum::get(albums, &sel); let active_album = IdSelectAlbum::get(albums, &sel);
sel.reinitialise(albums, active_album); sel.reinitialise(albums, active_album);
assert_eq!(sel, expected); assert_eq!(sel, expected);
// Re-initialise out-of-bounds. // Re-initialise out-of-bounds.
let mut expected = sel.clone(); let mut expected = sel.clone();
expected.decrement(albums, Delta::Line); expected.decrement(albums, Delta::Line);
let active_album = ActiveAlbum::get(albums, &sel); let active_album = IdSelectAlbum::get(albums, &sel);
sel.reinitialise(&albums[..(albums.len() - 1)], active_album); sel.reinitialise(&albums[..(albums.len() - 1)], active_album);
assert_eq!(sel, expected); assert_eq!(sel, expected);
// Re-initialise empty. // Re-initialise empty.
let expected = AlbumSelection::initialise(&[]); let expected = AlbumSelection::initialise(&[]);
let active_album = ActiveAlbum::get(albums, &sel); let active_album = IdSelectAlbum::get(albums, &sel);
sel.reinitialise(&[], active_album); sel.reinitialise(&[], active_album);
assert_eq!(sel, expected); assert_eq!(sel, expected);
} }
@ -870,20 +888,20 @@ mod tests {
// Re-initialise. // Re-initialise.
let expected = sel.clone(); let expected = sel.clone();
let active_artist = ActiveArtist::get(artists, &sel); let active_artist = IdSelectArtist::get(artists, &sel);
sel.reinitialise(artists, active_artist); sel.reinitialise(artists, active_artist);
assert_eq!(sel, expected); assert_eq!(sel, expected);
// Re-initialise out-of-bounds. // Re-initialise out-of-bounds.
let mut expected = sel.clone(); let mut expected = sel.clone();
expected.decrement(artists, Delta::Line); expected.decrement(artists, Delta::Line);
let active_artist = ActiveArtist::get(artists, &sel); let active_artist = IdSelectArtist::get(artists, &sel);
sel.reinitialise(&artists[..(artists.len() - 1)], active_artist); sel.reinitialise(&artists[..(artists.len() - 1)], active_artist);
assert_eq!(sel, expected); assert_eq!(sel, expected);
// Re-initialise empty. // Re-initialise empty.
let expected = ArtistSelection::initialise(&[]); let expected = ArtistSelection::initialise(&[]);
let active_artist = ActiveArtist::get(artists, &sel); let active_artist = IdSelectArtist::get(artists, &sel);
sel.reinitialise(&[], active_artist); sel.reinitialise(&[], active_artist);
assert_eq!(sel, expected); assert_eq!(sel, expected);
} }