Provide search functionality through the TUI #134
@ -224,6 +224,8 @@ 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());
|
||||||
|
self.selection
|
||||||
|
.reset_artist(self.music_hoard.get_collection());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,14 +276,24 @@ impl<MH: IMusicHoard> IAppInteractReloadPrivate for App<MH> {
|
|||||||
impl<MH: IMusicHoard> IAppInteractSearch for App<MH> {
|
impl<MH: IMusicHoard> IAppInteractSearch for App<MH> {
|
||||||
fn append_character(&mut self, ch: char) {
|
fn append_character(&mut self, ch: char) {
|
||||||
match self.state {
|
match self.state {
|
||||||
AppState::Search(ref mut s) => s.push(ch),
|
AppState::Search(ref mut s) => {
|
||||||
|
s.push(ch);
|
||||||
|
self.selection
|
||||||
|
.incremental_artist_search(self.music_hoard.get_collection(), s);
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_character(&mut self) {
|
fn remove_character(&mut self) {
|
||||||
match self.state {
|
match self.state {
|
||||||
AppState::Search(ref mut s) => s.pop(),
|
AppState::Search(ref mut s) => {
|
||||||
|
s.pop();
|
||||||
|
self.selection
|
||||||
|
.reset_artist(self.music_hoard.get_collection());
|
||||||
|
self.selection
|
||||||
|
.incremental_artist_search(self.music_hoard.get_collection(), s);
|
||||||
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,12 @@ impl Selection {
|
|||||||
self.artist.reinitialise(artists, selected.artist);
|
self.artist.reinitialise(artists, selected.artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reset_artist(&mut self, artists: &[Artist]) {
|
||||||
|
if self.artist.state.list.selected() != Some(0) {
|
||||||
|
self.select(artists, ActiveSelection { artist: None });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn increment_category(&mut self) {
|
pub fn increment_category(&mut self) {
|
||||||
self.active = match self.active {
|
self.active = match self.active {
|
||||||
Category::Artist => Category::Album,
|
Category::Artist => Category::Album,
|
||||||
@ -89,6 +95,10 @@ impl Selection {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn incremental_artist_search(&mut self, collection: &Collection, artist_name: &str) {
|
||||||
|
self.artist.incremental_search(collection, artist_name);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) {
|
pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) {
|
||||||
match self.active {
|
match self.active {
|
||||||
Category::Artist => self.increment_artist(collection, delta),
|
Category::Artist => self.increment_artist(collection, delta),
|
||||||
@ -172,16 +182,47 @@ impl ArtistSelection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_to(&mut self, artists: &[Artist], mut to: usize) {
|
||||||
|
if to >= artists.len() {
|
||||||
|
to = artists.len() - 1;
|
||||||
|
}
|
||||||
|
if self.state.list.selected() != Some(to) {
|
||||||
|
self.state.list.select(Some(to));
|
||||||
|
self.album = AlbumSelection::initialise(&artists[to].albums);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn incremental_search(&mut self, artists: &[Artist], artist_name: &str) {
|
||||||
|
if let Some(index) = self.state.list.selected() {
|
||||||
|
let case_sensitive = artist_name
|
||||||
|
.chars()
|
||||||
|
.any(|ch| !(ch.is_lowercase() || ch.is_whitespace()));
|
||||||
|
let slice = &artists[index..];
|
||||||
|
|
||||||
|
let result = if case_sensitive {
|
||||||
|
slice.binary_search_by(|probe| probe.get_sort_key().name.as_str().cmp(artist_name))
|
||||||
|
} else {
|
||||||
|
slice.binary_search_by(|probe| {
|
||||||
|
probe
|
||||||
|
.get_sort_key()
|
||||||
|
.name
|
||||||
|
.to_lowercase()
|
||||||
|
.as_str()
|
||||||
|
.cmp(artist_name)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_index = match result {
|
||||||
|
Ok(slice_index) | Err(slice_index) => index + slice_index,
|
||||||
|
};
|
||||||
|
self.select_to(artists, new_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn increment_by(&mut self, artists: &[Artist], by: usize) {
|
fn increment_by(&mut self, artists: &[Artist], by: usize) {
|
||||||
if let Some(index) = self.state.list.selected() {
|
if let Some(index) = self.state.list.selected() {
|
||||||
let mut result = index.saturating_add(by);
|
let result = index.saturating_add(by);
|
||||||
if result >= artists.len() {
|
self.select_to(artists, result);
|
||||||
result = artists.len() - 1;
|
|
||||||
}
|
|
||||||
if self.state.list.selected() != Some(result) {
|
|
||||||
self.state.list.select(Some(result));
|
|
||||||
self.album = AlbumSelection::initialise(&artists[result].albums);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,35 +53,35 @@ impl<APP: IAppInteract> IEventHandler<APP> for EventHandler {
|
|||||||
|
|
||||||
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||||
fn handle_key_event(app: &mut APP, key_event: KeyEvent) {
|
fn handle_key_event(app: &mut APP, key_event: KeyEvent) {
|
||||||
match key_event.code {
|
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||||
// Exit application on `Ctrl-C`.
|
match key_event.code {
|
||||||
KeyCode::Char('c') | KeyCode::Char('C') => {
|
// Exit application on `Ctrl-C`.
|
||||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
KeyCode::Char('c') | KeyCode::Char('C') => {
|
||||||
app.force_quit();
|
app.force_quit();
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match app.state() {
|
||||||
|
AppState::Browse(browse) => {
|
||||||
|
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event);
|
||||||
|
}
|
||||||
|
AppState::Info(info) => {
|
||||||
|
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event);
|
||||||
|
}
|
||||||
|
AppState::Reload(reload) => {
|
||||||
|
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event);
|
||||||
|
}
|
||||||
|
AppState::Search(search) => {
|
||||||
|
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, 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);
|
||||||
}
|
}
|
||||||
_ => match app.state() {
|
|
||||||
AppState::Browse(browse) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_browse_key_event(browse, key_event);
|
|
||||||
}
|
|
||||||
AppState::Info(info) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_info_key_event(info, key_event);
|
|
||||||
}
|
|
||||||
AppState::Reload(reload) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event);
|
|
||||||
}
|
|
||||||
AppState::Search(search) => {
|
|
||||||
<Self as IEventHandlerPrivate<APP>>::handle_search_key_event(search, 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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user