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,
};
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());

View File

@ -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();

View File

@ -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,
};