Provide search functionality through the TUI #134
@ -7,28 +7,33 @@ use crate::tui::{
|
||||
lib::IMusicHoard,
|
||||
};
|
||||
|
||||
pub enum AppState<BS, IS, RS, ES, CS> {
|
||||
pub enum AppState<BS, IS, RS, SS, ES, CS> {
|
||||
Browse(BS),
|
||||
Info(IS),
|
||||
Reload(RS),
|
||||
Search(SS),
|
||||
Error(ES),
|
||||
Critical(CS),
|
||||
}
|
||||
|
||||
impl<BS, IS, RS, ES, CS> AppState<BS, IS, RS, ES, CS> {
|
||||
fn is_browse(&self) -> bool {
|
||||
impl<BS, IS, RS, SS, ES, CS> AppState<BS, IS, RS, SS, ES, CS> {
|
||||
pub fn is_browse(&self) -> bool {
|
||||
matches!(self, AppState::Browse(_))
|
||||
}
|
||||
|
||||
fn is_info(&self) -> bool {
|
||||
pub fn is_info(&self) -> bool {
|
||||
matches!(self, AppState::Info(_))
|
||||
}
|
||||
|
||||
fn is_reload(&self) -> bool {
|
||||
pub fn is_reload(&self) -> bool {
|
||||
matches!(self, AppState::Reload(_))
|
||||
}
|
||||
|
||||
fn is_error(&self) -> bool {
|
||||
pub fn is_search(&self) -> bool {
|
||||
matches!(self, AppState::Search(_))
|
||||
}
|
||||
|
||||
pub fn is_error(&self) -> bool {
|
||||
matches!(self, AppState::Error(_))
|
||||
}
|
||||
}
|
||||
@ -37,6 +42,7 @@ pub trait IAppInteract {
|
||||
type BS: IAppInteractBrowse;
|
||||
type IS: IAppInteractInfo;
|
||||
type RS: IAppInteractReload;
|
||||
type SS: IAppInteractSearch;
|
||||
type ES: IAppInteractError;
|
||||
type CS: IAppInteractCritical;
|
||||
|
||||
@ -46,7 +52,14 @@ pub trait IAppInteract {
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn state(
|
||||
&mut self,
|
||||
) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES, &mut Self::CS>;
|
||||
) -> AppState<
|
||||
&mut Self::BS,
|
||||
&mut Self::IS,
|
||||
&mut Self::RS,
|
||||
&mut Self::SS,
|
||||
&mut Self::ES,
|
||||
&mut Self::CS,
|
||||
>;
|
||||
}
|
||||
|
||||
pub trait IAppInteractBrowse {
|
||||
@ -61,6 +74,8 @@ pub trait IAppInteractBrowse {
|
||||
fn show_info_overlay(&mut self);
|
||||
|
||||
fn show_reload_menu(&mut self);
|
||||
|
||||
fn begin_search(&mut self);
|
||||
}
|
||||
|
||||
pub trait IAppInteractInfo {
|
||||
@ -73,6 +88,12 @@ pub trait IAppInteractReload {
|
||||
fn hide_reload_menu(&mut self);
|
||||
}
|
||||
|
||||
pub trait IAppInteractSearch {
|
||||
fn append_character(&mut self, ch: char);
|
||||
fn remove_character(&mut self);
|
||||
fn finish_search(&mut self);
|
||||
}
|
||||
|
||||
pub trait IAppInteractError {
|
||||
fn dismiss_error(&mut self);
|
||||
}
|
||||
@ -87,7 +108,7 @@ pub trait IAppAccess {
|
||||
fn get(&mut self) -> AppPublic;
|
||||
}
|
||||
|
||||
pub type AppPublicState = AppState<(), (), (), String, String>;
|
||||
pub type AppPublicState = AppState<(), (), (), String, String, String>;
|
||||
|
||||
pub struct AppPublic<'app> {
|
||||
pub collection: &'app Collection,
|
||||
@ -99,7 +120,7 @@ pub struct App<MH: IMusicHoard> {
|
||||
running: bool,
|
||||
music_hoard: MH,
|
||||
selection: Selection,
|
||||
state: AppState<(), (), (), String, String>,
|
||||
state: AppState<(), (), (), String, String, String>,
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> App<MH> {
|
||||
@ -128,6 +149,7 @@ impl<MH: IMusicHoard> IAppInteract for App<MH> {
|
||||
type BS = Self;
|
||||
type IS = Self;
|
||||
type RS = Self;
|
||||
type SS = Self;
|
||||
type ES = Self;
|
||||
type CS = Self;
|
||||
|
||||
@ -141,11 +163,19 @@ impl<MH: IMusicHoard> IAppInteract for App<MH> {
|
||||
|
||||
fn state(
|
||||
&mut self,
|
||||
) -> AppState<&mut Self::BS, &mut Self::IS, &mut Self::RS, &mut Self::ES, &mut Self::CS> {
|
||||
) -> AppState<
|
||||
&mut Self::BS,
|
||||
&mut Self::IS,
|
||||
&mut Self::RS,
|
||||
&mut Self::SS,
|
||||
&mut Self::ES,
|
||||
&mut Self::CS,
|
||||
> {
|
||||
match self.state {
|
||||
AppState::Browse(_) => AppState::Browse(self),
|
||||
AppState::Info(_) => AppState::Info(self),
|
||||
AppState::Reload(_) => AppState::Reload(self),
|
||||
AppState::Search(_) => AppState::Search(self),
|
||||
AppState::Error(_) => AppState::Error(self),
|
||||
AppState::Critical(_) => AppState::Critical(self),
|
||||
}
|
||||
@ -190,6 +220,11 @@ impl<MH: IMusicHoard> IAppInteractBrowse for App<MH> {
|
||||
assert!(self.state.is_browse());
|
||||
self.state = AppState::Reload(());
|
||||
}
|
||||
|
||||
fn begin_search(&mut self) {
|
||||
assert!(self.state.is_browse());
|
||||
self.state = AppState::Search(String::new());
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractInfo for App<MH> {
|
||||
@ -236,6 +271,27 @@ impl<MH: IMusicHoard> IAppInteractReloadPrivate for App<MH> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractSearch for App<MH> {
|
||||
fn append_character(&mut self, ch: char) {
|
||||
match self.state {
|
||||
AppState::Search(ref mut s) => s.push(ch),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_character(&mut self) {
|
||||
match self.state {
|
||||
AppState::Search(ref mut s) => s.pop(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
fn finish_search(&mut self) {
|
||||
assert!(self.state.is_search());
|
||||
self.state = AppState::Browse(());
|
||||
}
|
||||
}
|
||||
|
||||
impl<MH: IMusicHoard> IAppInteractError for App<MH> {
|
||||
fn dismiss_error(&mut self) {
|
||||
assert!(self.state.is_error());
|
||||
|
@ -7,7 +7,7 @@ use crate::tui::{
|
||||
app::{
|
||||
app::{
|
||||
AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo,
|
||||
IAppInteractReload,
|
||||
IAppInteractReload, IAppInteractSearch,
|
||||
},
|
||||
selection::Delta,
|
||||
},
|
||||
@ -24,6 +24,7 @@ trait IEventHandlerPrivate<APP: IAppInteract> {
|
||||
fn handle_browse_key_event(app: &mut <APP as IAppInteract>::BS, key_event: KeyEvent);
|
||||
fn handle_info_key_event(app: &mut <APP as IAppInteract>::IS, key_event: KeyEvent);
|
||||
fn handle_reload_key_event(app: &mut <APP as IAppInteract>::RS, key_event: KeyEvent);
|
||||
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent);
|
||||
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, key_event: KeyEvent);
|
||||
fn handle_critical_key_event(app: &mut <APP as IAppInteract>::CS, key_event: KeyEvent);
|
||||
}
|
||||
@ -69,6 +70,9 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||
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);
|
||||
}
|
||||
@ -96,10 +100,16 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||
KeyCode::Down => app.increment_selection(Delta::Line),
|
||||
KeyCode::PageUp => app.decrement_selection(Delta::Page),
|
||||
KeyCode::PageDown => app.increment_selection(Delta::Page),
|
||||
// Toggle overlay.
|
||||
// Toggle info overlay.
|
||||
KeyCode::Char('m') | KeyCode::Char('M') => app.show_info_overlay(),
|
||||
// Toggle Reload
|
||||
// Toggle reload meny.
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => app.show_reload_menu(),
|
||||
// Toggle search.
|
||||
KeyCode::Char('s') | KeyCode::Char('S') => {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
app.begin_search();
|
||||
}
|
||||
}
|
||||
// Othey keys.
|
||||
_ => {}
|
||||
}
|
||||
@ -134,6 +144,18 @@ impl<APP: IAppInteract> IEventHandlerPrivate<APP> for EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_search_key_event(app: &mut <APP as IAppInteract>::SS, key_event: KeyEvent) {
|
||||
match key_event.code {
|
||||
// Add/remove character to search.
|
||||
KeyCode::Char(ch) => app.append_character(ch),
|
||||
KeyCode::Backspace => app.remove_character(),
|
||||
// Return.
|
||||
KeyCode::Esc | KeyCode::Enter => app.finish_search(),
|
||||
// Othey keys.
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, _key_event: KeyEvent) {
|
||||
// Any key dismisses the error.
|
||||
app.dismiss_error();
|
||||
|
@ -353,12 +353,13 @@ struct Minibuffer<'a> {
|
||||
|
||||
impl Minibuffer<'_> {
|
||||
fn paragraphs(state: &AppPublicState) -> Self {
|
||||
let columns = 2;
|
||||
match state {
|
||||
let columns = 3;
|
||||
let mut mb = match state {
|
||||
AppState::Browse(_) => Minibuffer {
|
||||
paragraphs: vec![
|
||||
Paragraph::new("m: show info overlay"),
|
||||
Paragraph::new("g: show reload menu"),
|
||||
Paragraph::new("ctrl+s: search artist"),
|
||||
],
|
||||
columns,
|
||||
},
|
||||
@ -374,6 +375,10 @@ impl Minibuffer<'_> {
|
||||
],
|
||||
columns,
|
||||
},
|
||||
AppState::Search(ref s) => Minibuffer {
|
||||
paragraphs: vec![Paragraph::new(format!("I-search: {s}"))],
|
||||
columns: 1,
|
||||
},
|
||||
AppState::Error(_) => Minibuffer {
|
||||
paragraphs: vec![Paragraph::new(
|
||||
"Press any key to dismiss the error message...",
|
||||
@ -384,7 +389,17 @@ impl Minibuffer<'_> {
|
||||
paragraphs: vec![Paragraph::new("Press ctrl+c to terminate the program...")],
|
||||
columns: 1,
|
||||
},
|
||||
};
|
||||
|
||||
if !state.is_search() {
|
||||
mb.paragraphs = mb
|
||||
.paragraphs
|
||||
.into_iter()
|
||||
.map(|p| p.alignment(Alignment::Center))
|
||||
.collect();
|
||||
}
|
||||
|
||||
mb
|
||||
}
|
||||
}
|
||||
|
||||
@ -553,17 +568,13 @@ impl Ui {
|
||||
}
|
||||
|
||||
fn render_minibuffer(state: &AppPublicState, ar: Rect, fr: &mut Frame) {
|
||||
let mut mb = Minibuffer::paragraphs(state);
|
||||
mb.paragraphs = mb
|
||||
.paragraphs
|
||||
.into_iter()
|
||||
.map(|p| p.alignment(Alignment::Center))
|
||||
.collect();
|
||||
let mb = Minibuffer::paragraphs(state);
|
||||
|
||||
let space = 3;
|
||||
let area = Rect {
|
||||
x: ar.x + 1,
|
||||
x: ar.x + 1 + space,
|
||||
y: ar.y + 1,
|
||||
width: ar.width.saturating_sub(2),
|
||||
width: ar.width.saturating_sub(2 + 2 * space),
|
||||
height: 1,
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user