diff --git a/src/tui/app/machine/input_state.rs b/src/tui/app/machine/input_state.rs index 52e0e7d..9595759 100644 --- a/src/tui/app/machine/input_state.rs +++ b/src/tui/app/machine/input_state.rs @@ -2,7 +2,7 @@ use tui_input::{backend::crossterm::EventHandler, Input}; use crate::tui::app::{ machine::{App, AppInner, AppMachine}, - AppPublic, AppState, IAppInteractInput, InputEvent, + AppPublic, AppState, IAppInteractInput, InputClientPublic, InputEvent, InputStatePublic, }; use super::match_state::MatchState; @@ -34,11 +34,22 @@ impl From> for App { } } +impl<'a> From<&'a mut InputClient> for InputClientPublic<'a> { + fn from(client: &'a mut InputClient) -> Self { + match client { + InputClient::Match(state) => InputClientPublic::Match(state.into()), + } + } +} + impl<'a> From<&'a mut AppMachine> for AppPublic<'a> { fn from(machine: &'a mut AppMachine) -> Self { AppPublic { inner: (&mut machine.inner).into(), - state: AppState::Input(&machine.state.input), + state: AppState::Input(InputStatePublic { + input: &machine.state.input, + client: (&mut machine.state.client).into(), + }), } } } @@ -47,19 +58,21 @@ impl IAppInteractInput for AppMachine { type APP = App; fn input(mut self, input: InputEvent) -> Self::APP { - self.state.input.handle_event(&crossterm::event::Event::Key(input)); + self.state + .input + .handle_event(&crossterm::event::Event::Key(input)); self.into() } fn confirm(self) -> Self::APP { match self.state.client { - InputClient::Match(state) => AppMachine::match_state(self.inner, state).into() + InputClient::Match(state) => AppMachine::match_state(self.inner, state).into(), } } fn cancel(self) -> Self::APP { match self.state.client { - InputClient::Match(state) => AppMachine::match_state(self.inner, state).into() + InputClient::Match(state) => AppMachine::match_state(self.inner, state).into(), } } } diff --git a/src/tui/app/machine/match_state.rs b/src/tui/app/machine/match_state.rs index ee8c593..4729ede 100644 --- a/src/tui/app/machine/match_state.rs +++ b/src/tui/app/machine/match_state.rs @@ -108,14 +108,20 @@ impl From> for App { } } +impl<'a> From<&'a mut MatchState> for MatchStatePublic<'a> { + fn from(state: &'a mut MatchState) -> Self { + MatchStatePublic { + info: state.current.as_ref().map(Into::into), + state: &mut state.state, + } + } +} + impl<'a> From<&'a mut AppMachine> for AppPublic<'a> { fn from(machine: &'a mut AppMachine) -> Self { AppPublic { inner: (&mut machine.inner).into(), - state: AppState::Match(MatchStatePublic { - info: machine.state.current.as_ref().map(Into::into), - state: &mut machine.state.state, - }), + state: AppState::Match((&mut machine.state).into()), } } } @@ -149,8 +155,14 @@ impl IAppInteractMatch for AppMachine { fn select(self) -> Self::APP { if let Some(index) = self.state.state.list.selected() { // selected() implies current exists - if self.state.current.as_ref().unwrap().is_manual_input_mbid(index) { - return AppMachine::input_state(self.inner, InputClient::Match(self.state)).into() + if self + .state + .current + .as_ref() + .unwrap() + .is_manual_input_mbid(index) + { + return AppMachine::input_state(self.inner, InputClient::Match(self.state)).into(); } } AppMachine::app_fetch_next(self.inner, self.state.fetch) diff --git a/src/tui/app/machine/mod.rs b/src/tui/app/machine/mod.rs index e56ec0b..d6b91dd 100644 --- a/src/tui/app/machine/mod.rs +++ b/src/tui/app/machine/mod.rs @@ -192,6 +192,7 @@ impl<'a> From<&'a mut AppInner> for AppPublicInner<'a> { mod tests { use std::sync::mpsc; + use input_state::InputClient; use musichoard::collection::Collection; use crate::tui::{ @@ -267,13 +268,6 @@ mod tests { } } - pub fn unwrap_input(self) -> InputState { - match self { - AppState::Input(input) => input, - _ => panic!(), - } - } - pub fn unwrap_error(self) -> ErrorState { match self { AppState::Error(error) => error, @@ -440,7 +434,7 @@ mod tests { } #[test] - fn state_matches() { + fn state_match() { let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); assert!(app.is_running()); @@ -465,6 +459,33 @@ mod tests { assert!(!app.is_running()); } + #[test] + fn state_input() { + let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); + assert!(app.is_running()); + + let (_, rx) = mpsc::channel(); + let fetch = FetchState::new(rx); + let match_state = MatchState::new(None, fetch); + let input_client = InputClient::Match(match_state); + app = AppMachine::input_state(app.unwrap_browse().inner, input_client).into(); + + let state = app.state(); + assert!(matches!(state, AppState::Input(_))); + app = state; + + app = app.no_op(); + let state = app.state(); + assert!(matches!(state, AppState::Input(_))); + app = state; + + let public = app.get(); + assert!(matches!(public.state, AppState::Input(_))); + + app = app.force_quit(); + assert!(!app.is_running()); + } + #[test] fn state_error() { let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); diff --git a/src/tui/app/mod.rs b/src/tui/app/mod.rs index aaaaedd..85e79bc 100644 --- a/src/tui/app/mod.rs +++ b/src/tui/app/mod.rs @@ -1,12 +1,10 @@ mod machine; mod selection; -use crossterm::event::KeyEvent; pub use machine::App; pub use selection::{Category, Delta, Selection, WidgetState}; use musichoard::collection::{album::AlbumMeta, artist::ArtistMeta, Collection}; -use tui_input::Input; use crate::tui::lib::interface::musicbrainz::Match; @@ -135,7 +133,7 @@ pub trait IAppInteractMatch { fn abort(self) -> Self::APP; } -type InputEvent = KeyEvent; +type InputEvent = crossterm::event::KeyEvent; pub trait IAppInteractInput { type APP: IApp; @@ -216,7 +214,15 @@ pub struct MatchStatePublic<'app> { pub state: &'app mut WidgetState, } -pub type InputStatePublic<'app> = &'app Input; +pub enum InputClientPublic<'app> { + Match(MatchStatePublic<'app>), +} + +pub type Input = tui_input::Input; +pub struct InputStatePublic<'app> { + pub input: &'app Input, + pub client: InputClientPublic<'app> +} pub type AppPublicState<'app> = AppState< (), diff --git a/src/tui/ui/mod.rs b/src/tui/ui/mod.rs index c78815b..5bacefe 100644 --- a/src/tui/ui/mod.rs +++ b/src/tui/ui/mod.rs @@ -32,7 +32,7 @@ use crate::tui::{ }, }; -use super::app::InputStatePublic; +use super::app::{Input, InputClientPublic}; pub trait IUi { fn render(app: &mut APP, frame: &mut Frame); @@ -149,16 +149,18 @@ impl Ui { UiWidget::render_overlay_list_widget(&st.matching, st.list, st.state, true, area, frame) } - fn render_input_overlay(input: InputStatePublic, frame: &mut Frame) { + fn render_input_overlay(input: &Input, frame: &mut Frame) { let area = OverlayBuilder::default() + .with_width(OverlaySize::MarginFactor(4)) .with_height(OverlaySize::Value(3)) .build(frame.area()); - UiWidget::render_overlay_widget("Input", Paragraph::new(input.value()), area, false, frame); + let text_area = format!(" {}", input.value()); + UiWidget::render_overlay_widget("Input", Paragraph::new(text_area), area, false, frame); - let width = area.width.max(3) - 3; // keep 2 for borders and 1 for cursor + let width = area.width.max(4) - 4; // keep 2 for borders, 1 for left-pad, and 1 for cursor let scroll = input.visual_scroll(width as usize); frame.set_cursor_position(( - area.x + ((input.visual_cursor()).max(scroll) - scroll) as u16 + 1, + area.x + ((input.visual_cursor()).max(scroll) - scroll) as u16 + 2, area.y + 1, )) } @@ -186,7 +188,14 @@ impl IUi for Ui { AppState::Reload(()) => Self::render_reload_overlay(frame), AppState::Fetch(()) => Self::render_fetch_overlay(frame), AppState::Match(public) => Self::render_match_overlay(public.info, public.state, frame), - AppState::Input(input) => Self::render_input_overlay(input, frame), + AppState::Input(input) => { + match input.client { + InputClientPublic::Match(public) => { + Self::render_match_overlay(public.info, public.state, frame) + } + } + Self::render_input_overlay(input.input, frame); + } AppState::Error(msg) => Self::render_error_overlay("Error", msg, frame), AppState::Critical(msg) => Self::render_error_overlay("Critical Error", msg, frame), _ => {} @@ -202,7 +211,10 @@ mod tests { }; use crate::tui::{ - app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic}, + app::{ + AppPublic, AppPublicInner, Delta, InputClientPublic, InputStatePublic, MatchOption, + MatchStatePublic, + }, lib::interface::musicbrainz::Match, testmod::COLLECTION, tests::terminal, @@ -228,7 +240,17 @@ mod tests { info: m.info, state: m.state, }), - AppState::Input(ref i) => AppState::Input(i), + AppState::Input(ref mut i) => AppState::Input(InputStatePublic { + input: i.input, + client: match i.client { + InputClientPublic::Match(ref mut m) => { + InputClientPublic::Match(MatchStatePublic { + info: m.info, + state: m.state, + }) + } + }, + }), AppState::Error(s) => AppState::Error(s), AppState::Critical(s) => AppState::Critical(s), },