Search mode input

This commit is contained in:
Wojciech Kozlowski 2024-02-11 09:05:04 +01:00
parent 973db2a753
commit 8fd11c4f57
3 changed files with 112 additions and 23 deletions

View File

@ -7,28 +7,33 @@ use crate::tui::{
lib::IMusicHoard, lib::IMusicHoard,
}; };
pub enum AppState<BS, IS, RS, ES, CS> { pub enum AppState<BS, IS, RS, SS, ES, CS> {
Browse(BS), Browse(BS),
Info(IS), Info(IS),
Reload(RS), Reload(RS),
Search(SS),
Error(ES), Error(ES),
Critical(CS), Critical(CS),
} }
impl<BS, IS, RS, ES, CS> AppState<BS, IS, RS, ES, CS> { impl<BS, IS, RS, SS, ES, CS> AppState<BS, IS, RS, SS, ES, CS> {
fn is_browse(&self) -> bool { pub fn is_browse(&self) -> bool {
matches!(self, AppState::Browse(_)) matches!(self, AppState::Browse(_))
} }
fn is_info(&self) -> bool { pub fn is_info(&self) -> bool {
matches!(self, AppState::Info(_)) matches!(self, AppState::Info(_))
} }
fn is_reload(&self) -> bool { pub fn is_reload(&self) -> bool {
matches!(self, AppState::Reload(_)) 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(_)) matches!(self, AppState::Error(_))
} }
} }
@ -37,6 +42,7 @@ pub trait IAppInteract {
type BS: IAppInteractBrowse; type BS: IAppInteractBrowse;
type IS: IAppInteractInfo; type IS: IAppInteractInfo;
type RS: IAppInteractReload; type RS: IAppInteractReload;
type SS: IAppInteractSearch;
type ES: IAppInteractError; type ES: IAppInteractError;
type CS: IAppInteractCritical; type CS: IAppInteractCritical;
@ -46,7 +52,14 @@ pub trait IAppInteract {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
fn state( fn state(
&mut self, &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 { pub trait IAppInteractBrowse {
@ -61,6 +74,8 @@ pub trait IAppInteractBrowse {
fn show_info_overlay(&mut self); fn show_info_overlay(&mut self);
fn show_reload_menu(&mut self); fn show_reload_menu(&mut self);
fn begin_search(&mut self);
} }
pub trait IAppInteractInfo { pub trait IAppInteractInfo {
@ -73,6 +88,12 @@ pub trait IAppInteractReload {
fn hide_reload_menu(&mut self); 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 { pub trait IAppInteractError {
fn dismiss_error(&mut self); fn dismiss_error(&mut self);
} }
@ -87,7 +108,7 @@ pub trait IAppAccess {
fn get(&mut self) -> AppPublic; fn get(&mut self) -> AppPublic;
} }
pub type AppPublicState = AppState<(), (), (), String, String>; pub type AppPublicState = AppState<(), (), (), String, String, String>;
pub struct AppPublic<'app> { pub struct AppPublic<'app> {
pub collection: &'app Collection, pub collection: &'app Collection,
@ -99,7 +120,7 @@ pub struct App<MH: IMusicHoard> {
running: bool, running: bool,
music_hoard: MH, music_hoard: MH,
selection: Selection, selection: Selection,
state: AppState<(), (), (), String, String>, state: AppState<(), (), (), String, String, String>,
} }
impl<MH: IMusicHoard> App<MH> { impl<MH: IMusicHoard> App<MH> {
@ -128,6 +149,7 @@ impl<MH: IMusicHoard> IAppInteract for App<MH> {
type BS = Self; type BS = Self;
type IS = Self; type IS = Self;
type RS = Self; type RS = Self;
type SS = Self;
type ES = Self; type ES = Self;
type CS = Self; type CS = Self;
@ -141,11 +163,19 @@ impl<MH: IMusicHoard> IAppInteract for App<MH> {
fn state( fn state(
&mut self, &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 { match self.state {
AppState::Browse(_) => AppState::Browse(self), AppState::Browse(_) => AppState::Browse(self),
AppState::Info(_) => AppState::Info(self), AppState::Info(_) => AppState::Info(self),
AppState::Reload(_) => AppState::Reload(self), AppState::Reload(_) => AppState::Reload(self),
AppState::Search(_) => AppState::Search(self),
AppState::Error(_) => AppState::Error(self), AppState::Error(_) => AppState::Error(self),
AppState::Critical(_) => AppState::Critical(self), AppState::Critical(_) => AppState::Critical(self),
} }
@ -190,6 +220,11 @@ impl<MH: IMusicHoard> IAppInteractBrowse for App<MH> {
assert!(self.state.is_browse()); assert!(self.state.is_browse());
self.state = AppState::Reload(()); 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> { 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> { impl<MH: IMusicHoard> IAppInteractError for App<MH> {
fn dismiss_error(&mut self) { fn dismiss_error(&mut self) {
assert!(self.state.is_error()); assert!(self.state.is_error());

View File

@ -7,7 +7,7 @@ use crate::tui::{
app::{ app::{
app::{ app::{
AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo, AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo,
IAppInteractReload, IAppInteractReload, IAppInteractSearch,
}, },
selection::Delta, 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_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_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_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_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); 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) => { AppState::Reload(reload) => {
<Self as IEventHandlerPrivate<APP>>::handle_reload_key_event(reload, key_event); <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) => { AppState::Error(error) => {
<Self as IEventHandlerPrivate<APP>>::handle_error_key_event(error, key_event); <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::Down => app.increment_selection(Delta::Line),
KeyCode::PageUp => app.decrement_selection(Delta::Page), KeyCode::PageUp => app.decrement_selection(Delta::Page),
KeyCode::PageDown => app.increment_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(), 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(), 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. // 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) { fn handle_error_key_event(app: &mut <APP as IAppInteract>::ES, _key_event: KeyEvent) {
// Any key dismisses the error. // Any key dismisses the error.
app.dismiss_error(); app.dismiss_error();

View File

@ -353,12 +353,13 @@ struct Minibuffer<'a> {
impl Minibuffer<'_> { impl Minibuffer<'_> {
fn paragraphs(state: &AppPublicState) -> Self { fn paragraphs(state: &AppPublicState) -> Self {
let columns = 2; let columns = 3;
match state { let mut mb = match state {
AppState::Browse(_) => Minibuffer { AppState::Browse(_) => Minibuffer {
paragraphs: vec![ paragraphs: vec![
Paragraph::new("m: show info overlay"), Paragraph::new("m: show info overlay"),
Paragraph::new("g: show reload menu"), Paragraph::new("g: show reload menu"),
Paragraph::new("ctrl+s: search artist"),
], ],
columns, columns,
}, },
@ -374,6 +375,10 @@ impl Minibuffer<'_> {
], ],
columns, columns,
}, },
AppState::Search(ref s) => Minibuffer {
paragraphs: vec![Paragraph::new(format!("I-search: {s}"))],
columns: 1,
},
AppState::Error(_) => Minibuffer { AppState::Error(_) => Minibuffer {
paragraphs: vec![Paragraph::new( paragraphs: vec![Paragraph::new(
"Press any key to dismiss the error message...", "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...")], paragraphs: vec![Paragraph::new("Press ctrl+c to terminate the program...")],
columns: 1, 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) { fn render_minibuffer(state: &AppPublicState, ar: Rect, fr: &mut Frame) {
let mut mb = Minibuffer::paragraphs(state); let mb = Minibuffer::paragraphs(state);
mb.paragraphs = mb
.paragraphs
.into_iter()
.map(|p| p.alignment(Alignment::Center))
.collect();
let space = 3;
let area = Rect { let area = Rect {
x: ar.x + 1, x: ar.x + 1 + space,
y: ar.y + 1, y: ar.y + 1,
width: ar.width.saturating_sub(2), width: ar.width.saturating_sub(2 + 2 * space),
height: 1, height: 1,
}; };