Extend incremental search to albums and tracks #152

Merged
wojtek merged 7 commits from 145---extend-incremental-search-to-albums-and-tracks into main 2024-03-01 22:04:26 +01:00
2 changed files with 88 additions and 60 deletions
Showing only changes of commit 32f6fbd39a - Show all commits

View File

@ -6,7 +6,7 @@ use musichoard::collection::{album::Album, artist::Artist, track::Track};
use crate::tui::{
app::{
machine::{App, AppInner, AppMachine},
selection::ListSelection,
selection::{ListSelection, SelectionState},
AppPublic, AppState, Category, IAppInteractSearch,
},
lib::IMusicHoard,
@ -109,9 +109,14 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
trait IAppInteractSearchPrivate {
fn incremental_search(&mut self, next: bool);
fn search_category<T, P>(list: &[T], index: usize, next: bool, pred: P) -> Option<usize>
fn search_category<T, P>(
state: SelectionState<'_, T>,
search_name: &str,
next: bool,
predicate: P,
) -> Option<usize>
where
P: FnMut(&T) -> bool;
P: FnMut(bool, bool, &str, &T) -> bool;
fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool;
fn predicate_albums(case_sens: bool, char_sens: bool, search: &str, probe: &Album) -> bool;
fn predicate_tracks(case_sens: bool, char_sens: bool, search: &str, probe: &Track) -> bool;
@ -125,73 +130,49 @@ trait IAppInteractSearchPrivate {
impl<MH: IMusicHoard> IAppInteractSearchPrivate for AppMachine<MH, AppSearch> {
fn incremental_search(&mut self, next: bool) {
let collection = self.inner.music_hoard.get_collection();
let search_name = &self.state.string;
let search = &self.state.string;
let sel = &mut self.inner.selection;
if let Some(index) = sel.selected() {
let case_sensitive = Self::is_case_sensitive(search_name);
let char_sensitive = Self::is_char_sensitive(search_name);
let search = Self::normalize_search(search_name, !case_sensitive, !char_sensitive);
let result = match sel.active {
Category::Artist => {
let artists = collection;
Self::search_category(artists, index, next, |probe| {
Self::predicate_artists(case_sensitive, char_sensitive, &search, probe)
})
}
Category::Album => {
let artists = collection;
let albums = sel
.artist
.state
.list
.selected()
.map(|i| &artists[i].albums)
.unwrap();
Self::search_category(albums, index, next, |probe| {
Self::predicate_albums(case_sensitive, char_sensitive, &search, probe)
})
}
Category::Track => {
let artists = collection;
let albums = sel
.artist
.state
.list
.selected()
.map(|i| &artists[i].albums)
.unwrap();
let tracks = sel
.artist
.album
.state
.list
.selected()
.map(|i| &albums[i].tracks)
.unwrap();
Self::search_category(tracks, index, next, |probe| {
Self::predicate_tracks(case_sensitive, char_sensitive, &search, probe)
})
}
Category::Artist => sel.state_artist(collection).and_then(|state| {
Self::search_category(state, search, next, Self::predicate_artists)
}),
Category::Album => sel.state_album(collection).and_then(|state| {
Self::search_category(state, search, next, Self::predicate_albums)
}),
Category::Track => sel.state_track(collection).and_then(|state| {
Self::search_category(state, search, next, Self::predicate_tracks)
}),
};
if result.is_some() {
sel.select(collection, result);
}
}
}
fn search_category<T, P>(list: &[T], mut index: usize, next: bool, pred: P) -> Option<usize>
fn search_category<T, P>(
state: SelectionState<'_, T>,
search_name: &str,
next: bool,
mut predicate: P,
) -> Option<usize>
where
P: FnMut(&T) -> bool,
P: FnMut(bool, bool, &str, &T) -> bool,
{
if next && ((index + 1) < list.len()) {
let case_sens = Self::is_case_sensitive(search_name);
let char_sens = Self::is_char_sensitive(search_name);
let search = Self::normalize_search(search_name, !case_sens, !char_sens);
let mut index = state.index;
if next && ((index + 1) < state.list.len()) {
index += 1;
}
let slice = &list[index..];
let result = slice.iter().position(pred);
result.map(|slice_index| index + slice_index)
let slice = &state.list[index..];
slice
.iter()
.position(|probe| predicate(case_sens, char_sens, &search, probe))
.map(|slice_index| index + slice_index)
}
fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool {

View File

@ -62,6 +62,11 @@ impl Delta {
}
}
pub struct SelectionState<'a, T> {
pub list: &'a [T],
pub index: usize,
}
impl Selection {
pub fn new(artists: &[Artist]) -> Self {
Selection {
@ -136,6 +141,18 @@ impl Selection {
self.artist.selected_track()
}
pub fn state_artist<'a>(&self, coll: &'a Collection) -> Option<SelectionState<'a, Artist>> {
self.artist.selection_state(coll)
}
pub fn state_album<'a>(&self, coll: &'a Collection) -> Option<SelectionState<'a, Album>> {
self.artist.state_album(coll)
}
pub fn state_track<'a>(&self, coll: &'a Collection) -> Option<SelectionState<'a, Track>> {
self.artist.state_track(coll)
}
pub fn reset(&mut self, collection: &Collection) {
match self.active {
Category::Artist => self.reset_artist(collection),
@ -278,6 +295,21 @@ impl ArtistSelection {
}
}
fn selection_state<'a>(&self, list: &'a [Artist]) -> Option<SelectionState<'a, Artist>> {
let selected = self.state.list.selected();
selected.map(|index| SelectionState { list, index })
}
fn state_album<'a>(&self, artists: &'a [Artist]) -> Option<SelectionState<'a, Album>> {
let selected = self.state.list.selected();
selected.and_then(|index| self.album.selection_state(&artists[index].albums))
}
fn state_track<'a>(&self, artists: &'a [Artist]) -> Option<SelectionState<'a, Track>> {
let selected = self.state.list.selected();
selected.and_then(|index| self.album.state_tracks(&artists[index].albums))
}
fn reset(&mut self, artists: &[Artist]) {
if self.state.list.selected() != Some(0) {
self.reinitialise(artists, None);
@ -416,6 +448,16 @@ impl AlbumSelection {
}
}
fn selection_state<'a>(&self, list: &'a [Album]) -> Option<SelectionState<'a, Album>> {
let selected = self.state.list.selected();
selected.map(|index| SelectionState { list, index })
}
fn state_tracks<'a>(&self, albums: &'a [Album]) -> Option<SelectionState<'a, Track>> {
let selected = self.state.list.selected();
selected.and_then(|index| self.track.selection_state(&albums[index].tracks))
}
fn reset(&mut self, albums: &[Album]) {
if self.state.list.selected() != Some(0) {
self.reinitialise(albums, None);
@ -518,6 +560,11 @@ impl TrackSelection {
self.state.list.select(Some(to));
}
fn selection_state<'a>(&self, list: &'a [Track]) -> Option<SelectionState<'a, Track>> {
let selected = self.state.list.selected();
selected.map(|index| SelectionState { list, index })
}
fn reset(&mut self, tracks: &[Track]) {
if self.state.list.selected() != Some(0) {
self.reinitialise(tracks, None);