Input no longer a state
This commit is contained in:
parent
09e1cb7fda
commit
8acf208968
@ -11,6 +11,7 @@ impl AppMachine<BrowseState> {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: BrowseState,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -26,6 +27,7 @@ impl<'a> From<&'a mut AppMachine<BrowseState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Browse(()),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ impl AppMachine<CriticalState> {
|
||||
state: CriticalState {
|
||||
string: string.into(),
|
||||
},
|
||||
input: None
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,6 +30,7 @@ impl<'a> From<&'a mut AppMachine<CriticalState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Critical(&machine.state.string),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ impl AppMachine<ErrorState> {
|
||||
state: ErrorState {
|
||||
string: string.into(),
|
||||
},
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,6 +30,7 @@ impl<'a> From<&'a mut AppMachine<ErrorState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Error(&machine.state.string),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,11 @@ pub type FetchReceiver = mpsc::Receiver<FetchResult>;
|
||||
|
||||
impl AppMachine<FetchState> {
|
||||
fn fetch_state(inner: AppInner, state: FetchState) -> Self {
|
||||
AppMachine { inner, state }
|
||||
AppMachine {
|
||||
inner,
|
||||
state,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn app_fetch_new(inner: AppInner) -> App {
|
||||
@ -173,6 +177,7 @@ impl<'a> From<&'a mut AppMachine<FetchState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Fetch(()),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ impl AppMachine<InfoState> {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: InfoState,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,6 +26,7 @@ impl<'a> From<&'a mut AppMachine<InfoState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Info(()),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
src/tui/app/machine/input.rs
Normal file
44
src/tui/app/machine/input.rs
Normal file
@ -0,0 +1,44 @@
|
||||
use tui_input::backend::crossterm::EventHandler;
|
||||
|
||||
use crate::tui::app::{
|
||||
machine::{App, AppMachine},
|
||||
IAppInput, InputEvent, InputPublic,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Input(tui_input::Input);
|
||||
|
||||
impl<'app> From<&'app Input> for InputPublic<'app> {
|
||||
fn from(value: &'app Input) -> Self {
|
||||
&value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<State> IAppInput for AppMachine<State>
|
||||
where
|
||||
AppMachine<State>: Into<App>,
|
||||
{
|
||||
type APP = App;
|
||||
|
||||
fn taking_input(&self) -> bool {
|
||||
self.input.is_some()
|
||||
}
|
||||
|
||||
fn input(mut self, input: InputEvent) -> Self::APP {
|
||||
self.input
|
||||
.as_mut()
|
||||
.unwrap() // FIXME
|
||||
.0
|
||||
.handle_event(&crossterm::event::Event::Key(input));
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn confirm(self) -> Self::APP {
|
||||
self.cancel()
|
||||
}
|
||||
|
||||
fn cancel(mut self) -> Self::APP {
|
||||
self.input = None;
|
||||
self.into()
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
use tui_input::{backend::crossterm::EventHandler, Input};
|
||||
|
||||
use crate::tui::app::{
|
||||
machine::{App, AppInner, AppMachine},
|
||||
AppPublic, AppState, IAppInteractInput, InputClientPublic, InputEvent, InputStatePublic,
|
||||
};
|
||||
|
||||
use super::match_state::MatchState;
|
||||
|
||||
pub struct InputState {
|
||||
input: Input,
|
||||
client: InputClient,
|
||||
}
|
||||
|
||||
pub enum InputClient {
|
||||
Match(MatchState),
|
||||
}
|
||||
|
||||
impl AppMachine<InputState> {
|
||||
pub fn input_state(inner: AppInner, client: InputClient) -> Self {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: InputState {
|
||||
input: Input::default(),
|
||||
client,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AppMachine<InputState>> for App {
|
||||
fn from(machine: AppMachine<InputState>) -> Self {
|
||||
AppState::Input(machine)
|
||||
}
|
||||
}
|
||||
|
||||
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<InputState>> for AppPublic<'a> {
|
||||
fn from(machine: &'a mut AppMachine<InputState>) -> Self {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Input(InputStatePublic {
|
||||
input: &machine.state.input,
|
||||
client: (&mut machine.state.client).into(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IAppInteractInput for AppMachine<InputState> {
|
||||
type APP = App;
|
||||
|
||||
fn input(mut self, input: InputEvent) -> Self::APP {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cancel(self) -> Self::APP {
|
||||
match self.state.client {
|
||||
InputClient::Match(state) => AppMachine::match_state(self.inner, state).into(),
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ use crate::tui::app::{
|
||||
MatchStateInfo, MatchStatePublic, WidgetState,
|
||||
};
|
||||
|
||||
use super::{fetch_state::FetchState, input_state::InputClient};
|
||||
use super::{fetch_state::FetchState, input::Input};
|
||||
|
||||
impl ArtistMatches {
|
||||
fn len(&self) -> usize {
|
||||
@ -98,7 +98,11 @@ impl MatchState {
|
||||
|
||||
impl AppMachine<MatchState> {
|
||||
pub fn match_state(inner: AppInner, state: MatchState) -> Self {
|
||||
AppMachine { inner, state }
|
||||
AppMachine {
|
||||
inner,
|
||||
state,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,6 +126,7 @@ impl<'a> From<&'a mut AppMachine<MatchState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Match((&mut machine.state).into()),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,7 +157,7 @@ impl IAppInteractMatch for AppMachine<MatchState> {
|
||||
self.into()
|
||||
}
|
||||
|
||||
fn select(self) -> Self::APP {
|
||||
fn select(mut self) -> Self::APP {
|
||||
if let Some(index) = self.state.state.list.selected() {
|
||||
// selected() implies current exists
|
||||
if self
|
||||
@ -162,7 +167,8 @@ impl IAppInteractMatch for AppMachine<MatchState> {
|
||||
.unwrap()
|
||||
.is_manual_input_mbid(index)
|
||||
{
|
||||
return AppMachine::input_state(self.inner, InputClient::Match(self.state)).into();
|
||||
self.input = Some(Input::default());
|
||||
return self.into();
|
||||
}
|
||||
}
|
||||
AppMachine::app_fetch_next(self.inner, self.state.fetch)
|
||||
|
@ -3,7 +3,7 @@ mod critical_state;
|
||||
mod error_state;
|
||||
mod fetch_state;
|
||||
mod info_state;
|
||||
mod input_state;
|
||||
mod input;
|
||||
mod match_state;
|
||||
mod reload_state;
|
||||
mod search_state;
|
||||
@ -21,11 +21,12 @@ use critical_state::CriticalState;
|
||||
use error_state::ErrorState;
|
||||
use fetch_state::FetchState;
|
||||
use info_state::InfoState;
|
||||
use input_state::InputState;
|
||||
use match_state::MatchState;
|
||||
use reload_state::ReloadState;
|
||||
use search_state::SearchState;
|
||||
|
||||
use input::Input;
|
||||
|
||||
use super::IAppBase;
|
||||
|
||||
pub type App = AppState<
|
||||
@ -35,7 +36,6 @@ pub type App = AppState<
|
||||
AppMachine<SearchState>,
|
||||
AppMachine<FetchState>,
|
||||
AppMachine<MatchState>,
|
||||
AppMachine<InputState>,
|
||||
AppMachine<ErrorState>,
|
||||
AppMachine<CriticalState>,
|
||||
>;
|
||||
@ -43,6 +43,7 @@ pub type App = AppState<
|
||||
pub struct AppMachine<STATE> {
|
||||
inner: AppInner,
|
||||
state: STATE,
|
||||
input: Option<Input>,
|
||||
}
|
||||
|
||||
pub struct AppInner {
|
||||
@ -80,7 +81,6 @@ impl App {
|
||||
AppState::Search(search_state) => &search_state.inner,
|
||||
AppState::Fetch(fetch_state) => &fetch_state.inner,
|
||||
AppState::Match(match_state) => &match_state.inner,
|
||||
AppState::Input(input_state) => &input_state.inner,
|
||||
AppState::Error(error_state) => &error_state.inner,
|
||||
AppState::Critical(critical_state) => &critical_state.inner,
|
||||
}
|
||||
@ -94,7 +94,6 @@ impl App {
|
||||
AppState::Search(search_state) => &mut search_state.inner,
|
||||
AppState::Fetch(fetch_state) => &mut fetch_state.inner,
|
||||
AppState::Match(match_state) => &mut match_state.inner,
|
||||
AppState::Input(input_state) => &mut input_state.inner,
|
||||
AppState::Error(error_state) => &mut error_state.inner,
|
||||
AppState::Critical(critical_state) => &mut critical_state.inner,
|
||||
}
|
||||
@ -108,7 +107,6 @@ impl IApp for App {
|
||||
type SearchState = AppMachine<SearchState>;
|
||||
type FetchState = AppMachine<FetchState>;
|
||||
type MatchState = AppMachine<MatchState>;
|
||||
type InputState = AppMachine<InputState>;
|
||||
type ErrorState = AppMachine<ErrorState>;
|
||||
type CriticalState = AppMachine<CriticalState>;
|
||||
|
||||
@ -130,7 +128,6 @@ impl IApp for App {
|
||||
Self::SearchState,
|
||||
Self::FetchState,
|
||||
Self::MatchState,
|
||||
Self::InputState,
|
||||
Self::ErrorState,
|
||||
Self::CriticalState,
|
||||
> {
|
||||
@ -155,7 +152,6 @@ impl IAppAccess for App {
|
||||
AppState::Search(state) => state.into(),
|
||||
AppState::Fetch(state) => state.into(),
|
||||
AppState::Match(state) => state.into(),
|
||||
AppState::Input(state) => state.into(),
|
||||
AppState::Error(state) => state.into(),
|
||||
AppState::Critical(state) => state.into(),
|
||||
}
|
||||
@ -192,7 +188,6 @@ 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::{
|
||||
@ -210,7 +205,6 @@ mod tests {
|
||||
SearchState,
|
||||
FetchState,
|
||||
MatchState,
|
||||
InputState,
|
||||
ErrorState,
|
||||
CriticalState,
|
||||
>
|
||||
@ -221,7 +215,6 @@ mod tests {
|
||||
SearchState,
|
||||
FetchState,
|
||||
MatchState,
|
||||
InputState,
|
||||
ErrorState,
|
||||
CriticalState,
|
||||
>
|
||||
@ -415,7 +408,12 @@ mod tests {
|
||||
let (_, rx) = mpsc::channel();
|
||||
let inner = app.unwrap_browse().inner;
|
||||
let state = FetchState::new(rx);
|
||||
app = AppMachine { inner, state }.into();
|
||||
app = AppMachine {
|
||||
inner,
|
||||
state,
|
||||
input: None,
|
||||
}
|
||||
.into();
|
||||
|
||||
let state = app.state();
|
||||
assert!(matches!(state, AppState::Fetch(_)));
|
||||
@ -459,33 +457,6 @@ 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());
|
||||
|
@ -11,6 +11,7 @@ impl AppMachine<ReloadState> {
|
||||
AppMachine {
|
||||
inner,
|
||||
state: ReloadState,
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -25,6 +26,7 @@ impl<'a> From<&'a mut AppMachine<ReloadState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Reload(()),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ impl AppMachine<SearchState> {
|
||||
orig,
|
||||
memo: vec![],
|
||||
},
|
||||
input: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,6 +56,7 @@ impl<'a> From<&'a mut AppMachine<SearchState>> for AppPublic<'a> {
|
||||
AppPublic {
|
||||
inner: (&mut machine.inner).into(),
|
||||
state: AppState::Search(&machine.state.string),
|
||||
input: (&machine.input.as_ref()).map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ pub enum AppState<
|
||||
SearchState,
|
||||
FetchState,
|
||||
MatchState,
|
||||
InputState,
|
||||
ErrorState,
|
||||
CriticalState,
|
||||
> {
|
||||
@ -25,7 +24,6 @@ pub enum AppState<
|
||||
Search(SearchState),
|
||||
Fetch(FetchState),
|
||||
Match(MatchState),
|
||||
Input(InputState),
|
||||
Error(ErrorState),
|
||||
Critical(CriticalState),
|
||||
}
|
||||
@ -38,8 +36,7 @@ pub trait IApp {
|
||||
type FetchState: IAppBase<APP = Self>
|
||||
+ IAppInteractFetch<APP = Self>
|
||||
+ IAppEventFetch<APP = Self>;
|
||||
type MatchState: IAppBase<APP = Self> + IAppInteractMatch<APP = Self>;
|
||||
type InputState: IAppBase<APP = Self> + IAppInteractInput<APP = Self>;
|
||||
type MatchState: IAppBase<APP = Self> + IAppInteractMatch<APP = Self> + IAppInput<APP = Self>;
|
||||
type ErrorState: IAppBase<APP = Self> + IAppInteractError<APP = Self>;
|
||||
type CriticalState: IAppBase<APP = Self>;
|
||||
|
||||
@ -56,7 +53,6 @@ pub trait IApp {
|
||||
Self::SearchState,
|
||||
Self::FetchState,
|
||||
Self::MatchState,
|
||||
Self::InputState,
|
||||
Self::ErrorState,
|
||||
Self::CriticalState,
|
||||
>;
|
||||
@ -134,9 +130,11 @@ pub trait IAppInteractMatch {
|
||||
}
|
||||
|
||||
type InputEvent = crossterm::event::KeyEvent;
|
||||
pub trait IAppInteractInput {
|
||||
pub trait IAppInput {
|
||||
type APP: IApp;
|
||||
|
||||
fn taking_input(&self) -> bool;
|
||||
|
||||
fn input(self, input: InputEvent) -> Self::APP;
|
||||
fn confirm(self) -> Self::APP;
|
||||
fn cancel(self) -> Self::APP;
|
||||
@ -159,6 +157,7 @@ pub trait IAppAccess {
|
||||
pub struct AppPublic<'app> {
|
||||
pub inner: AppPublicInner<'app>,
|
||||
pub state: AppPublicState<'app>,
|
||||
pub input: AppPublicInput<'app>,
|
||||
}
|
||||
|
||||
pub struct AppPublicInner<'app> {
|
||||
@ -214,27 +213,8 @@ pub struct MatchStatePublic<'app> {
|
||||
pub state: &'app mut WidgetState,
|
||||
}
|
||||
|
||||
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<
|
||||
(),
|
||||
(),
|
||||
(),
|
||||
&'app str,
|
||||
(),
|
||||
MatchStatePublic<'app>,
|
||||
InputStatePublic<'app>,
|
||||
&'app str,
|
||||
&'app str,
|
||||
>;
|
||||
pub type AppPublicState<'app> =
|
||||
AppState<(), (), (), &'app str, (), MatchStatePublic<'app>, &'app str, &'app str>;
|
||||
|
||||
impl<
|
||||
BrowseState,
|
||||
@ -243,7 +223,6 @@ impl<
|
||||
SearchState,
|
||||
FetchState,
|
||||
MatchState,
|
||||
InputState,
|
||||
ErrorState,
|
||||
CriticalState,
|
||||
>
|
||||
@ -254,7 +233,6 @@ impl<
|
||||
SearchState,
|
||||
FetchState,
|
||||
MatchState,
|
||||
InputState,
|
||||
ErrorState,
|
||||
CriticalState,
|
||||
>
|
||||
@ -264,6 +242,9 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
pub type InputPublic<'app> = &'app tui_input::Input;
|
||||
pub type AppPublicInput<'app> = Option<InputPublic<'app>>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -11,7 +11,7 @@ use crate::tui::{
|
||||
event::{Event, EventError, EventReceiver},
|
||||
};
|
||||
|
||||
use super::app::{IAppBase, IAppEventFetch, IAppInteractInput};
|
||||
use super::app::{IAppBase, IAppEventFetch, IAppInput};
|
||||
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait IEventHandler<APP: IApp> {
|
||||
@ -26,11 +26,12 @@ trait IEventHandlerPrivate<APP: IApp> {
|
||||
fn handle_search_key_event(app: <APP as IApp>::SearchState, key_event: KeyEvent) -> APP;
|
||||
fn handle_fetch_key_event(app: <APP as IApp>::FetchState, key_event: KeyEvent) -> APP;
|
||||
fn handle_match_key_event(app: <APP as IApp>::MatchState, key_event: KeyEvent) -> APP;
|
||||
fn handle_input_key_event(app: <APP as IApp>::InputState, key_event: KeyEvent) -> APP;
|
||||
fn handle_error_key_event(app: <APP as IApp>::ErrorState, key_event: KeyEvent) -> APP;
|
||||
fn handle_critical_key_event(app: <APP as IApp>::CriticalState, key_event: KeyEvent) -> APP;
|
||||
|
||||
fn handle_fetch_result_ready_event(app: APP) -> APP;
|
||||
|
||||
fn handle_input_key_event<Input: IAppInput<APP = APP>>(app: Input, key_event: KeyEvent) -> APP;
|
||||
}
|
||||
|
||||
pub struct EventHandler {
|
||||
@ -75,8 +76,13 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||
Self::handle_search_key_event(search_state, key_event)
|
||||
}
|
||||
AppState::Fetch(fetch_state) => Self::handle_fetch_key_event(fetch_state, key_event),
|
||||
AppState::Match(match_state) => Self::handle_match_key_event(match_state, key_event),
|
||||
AppState::Input(input_state) => Self::handle_input_key_event(input_state, key_event),
|
||||
AppState::Match(match_state) => {
|
||||
if match_state.taking_input() {
|
||||
Self::handle_input_key_event(match_state, key_event)
|
||||
} else {
|
||||
Self::handle_match_key_event(match_state, key_event)
|
||||
}
|
||||
}
|
||||
AppState::Error(error_state) => Self::handle_error_key_event(error_state, key_event),
|
||||
AppState::Critical(critical_state) => {
|
||||
Self::handle_critical_key_event(critical_state, key_event)
|
||||
@ -92,7 +98,6 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||
AppState::Search(state) => state.no_op(),
|
||||
AppState::Fetch(fetch_state) => fetch_state.fetch_result_ready(),
|
||||
AppState::Match(state) => state.no_op(),
|
||||
AppState::Input(state) => state.no_op(),
|
||||
AppState::Error(state) => state.no_op(),
|
||||
AppState::Critical(state) => state.no_op(),
|
||||
}
|
||||
@ -178,6 +183,13 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||
}
|
||||
|
||||
fn handle_fetch_key_event(app: <APP as IApp>::FetchState, key_event: KeyEvent) -> APP {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
return match key_event.code {
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => app.abort(),
|
||||
_ => app.no_op(),
|
||||
}
|
||||
}
|
||||
|
||||
match key_event.code {
|
||||
// Abort.
|
||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
|
||||
@ -187,6 +199,13 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||
}
|
||||
|
||||
fn handle_match_key_event(app: <APP as IApp>::MatchState, key_event: KeyEvent) -> APP {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
return match key_event.code {
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => app.abort(),
|
||||
_ => app.no_op(),
|
||||
}
|
||||
}
|
||||
|
||||
match key_event.code {
|
||||
// Abort.
|
||||
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
|
||||
@ -199,22 +218,6 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_input_key_event(app: <APP as IApp>::InputState, key_event: KeyEvent) -> APP {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
match key_event.code {
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => return app.cancel(),
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
|
||||
match key_event.code {
|
||||
// Return.
|
||||
KeyCode::Esc | KeyCode::Enter => app.confirm(),
|
||||
// Othey keys.
|
||||
_ => app.input(key_event),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_error_key_event(app: <APP as IApp>::ErrorState, _key_event: KeyEvent) -> APP {
|
||||
// Any key dismisses the error.
|
||||
app.dismiss_error()
|
||||
@ -224,5 +227,22 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
|
||||
// No action is allowed.
|
||||
app.no_op()
|
||||
}
|
||||
|
||||
fn handle_input_key_event<Input: IAppInput<APP = APP>>(app: Input, key_event: KeyEvent) -> APP {
|
||||
if key_event.modifiers == KeyModifiers::CONTROL {
|
||||
match key_event.code {
|
||||
KeyCode::Char('g') | KeyCode::Char('G') => return app.cancel(),
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
match key_event.code {
|
||||
// Return.
|
||||
KeyCode::Esc => app.cancel(),
|
||||
KeyCode::Enter => app.confirm(),
|
||||
// Othey keys.
|
||||
_ => app.input(key_event),
|
||||
}
|
||||
}
|
||||
}
|
||||
// GRCOV_EXCL_STOP
|
||||
|
@ -28,10 +28,14 @@ pub struct TrackArea {
|
||||
pub info: Rect,
|
||||
}
|
||||
|
||||
pub struct FrameArea {
|
||||
pub struct BrowseArea {
|
||||
pub artist: ArtistArea,
|
||||
pub album: AlbumArea,
|
||||
pub track: TrackArea,
|
||||
}
|
||||
|
||||
pub struct FrameArea {
|
||||
pub browse: BrowseArea,
|
||||
pub minibuffer: Rect,
|
||||
}
|
||||
|
||||
@ -91,6 +95,7 @@ impl FrameArea {
|
||||
};
|
||||
|
||||
FrameArea {
|
||||
browse: BrowseArea {
|
||||
artist: ArtistArea { list: artist_list },
|
||||
album: AlbumArea {
|
||||
list: album_list,
|
||||
@ -100,6 +105,7 @@ impl FrameArea {
|
||||
list: track_list,
|
||||
info: track_info,
|
||||
},
|
||||
},
|
||||
minibuffer,
|
||||
}
|
||||
}
|
||||
|
@ -57,20 +57,16 @@ impl Minibuffer<'_> {
|
||||
columns,
|
||||
},
|
||||
AppState::Fetch(()) => Minibuffer {
|
||||
paragraphs: vec![Paragraph::new("fetching..."), Paragraph::new("q: abort")],
|
||||
paragraphs: vec![
|
||||
Paragraph::new("fetching..."),
|
||||
Paragraph::new("ctrl+g: abort"),
|
||||
],
|
||||
columns: 2,
|
||||
},
|
||||
AppState::Match(public) => Minibuffer {
|
||||
paragraphs: vec![
|
||||
Paragraph::new(UiDisplay::display_matching_info(public.info)),
|
||||
Paragraph::new("q: abort"),
|
||||
],
|
||||
columns: 2,
|
||||
},
|
||||
AppState::Input(_) => Minibuffer {
|
||||
paragraphs: vec![
|
||||
Paragraph::new("enter: confirm"),
|
||||
Paragraph::new("ctrl+g: cancel"),
|
||||
Paragraph::new("ctrl+g: abort"),
|
||||
],
|
||||
columns: 2,
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ mod reload_state;
|
||||
mod style;
|
||||
mod widgets;
|
||||
|
||||
use browse_state::BrowseArea;
|
||||
use ratatui::{layout::Rect, widgets::Paragraph, Frame};
|
||||
|
||||
use musichoard::collection::{album::Album, Collection};
|
||||
@ -32,7 +33,7 @@ use crate::tui::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::app::{Input, InputClientPublic};
|
||||
use super::app::InputPublic;
|
||||
|
||||
pub trait IUi {
|
||||
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
|
||||
@ -66,11 +67,10 @@ impl Ui {
|
||||
fn render_browse_frame(
|
||||
artists: &Collection,
|
||||
selection: &mut Selection,
|
||||
state: &AppPublicState,
|
||||
areas: BrowseArea,
|
||||
frame: &mut Frame,
|
||||
) {
|
||||
let active = selection.category();
|
||||
let areas = FrameArea::new(frame.area());
|
||||
|
||||
let artist_state = ArtistState::new(
|
||||
active == Category::Artist,
|
||||
@ -103,8 +103,6 @@ impl Ui {
|
||||
);
|
||||
|
||||
Self::render_track_column(track_state, areas.track, frame);
|
||||
|
||||
Self::render_minibuffer(state, areas.minibuffer, frame);
|
||||
}
|
||||
|
||||
fn render_info_overlay(artists: &Collection, selection: &mut Selection, frame: &mut Frame) {
|
||||
@ -149,7 +147,7 @@ impl Ui {
|
||||
UiWidget::render_overlay_list_widget(&st.matching, st.list, st.state, true, area, frame)
|
||||
}
|
||||
|
||||
fn render_input_overlay(input: &Input, frame: &mut Frame) {
|
||||
fn render_input_overlay(input: InputPublic, frame: &mut Frame) {
|
||||
let area = OverlayBuilder::default()
|
||||
.with_width(OverlaySize::MarginFactor(4))
|
||||
.with_height(OverlaySize::Value(3))
|
||||
@ -182,24 +180,24 @@ impl IUi for Ui {
|
||||
let selection = app.inner.selection;
|
||||
let state = app.state;
|
||||
|
||||
Self::render_browse_frame(collection, selection, &state, frame);
|
||||
let areas = FrameArea::new(frame.area());
|
||||
|
||||
Self::render_browse_frame(collection, selection, areas.browse, frame);
|
||||
Self::render_minibuffer(&state, areas.minibuffer, frame);
|
||||
|
||||
match state {
|
||||
AppState::Info(()) => Self::render_info_overlay(collection, selection, frame),
|
||||
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) => {
|
||||
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),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if let Some(input) = app.input {
|
||||
Self::render_input_overlay(input, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,10 +209,7 @@ mod tests {
|
||||
};
|
||||
|
||||
use crate::tui::{
|
||||
app::{
|
||||
AppPublic, AppPublicInner, Delta, InputClientPublic, InputStatePublic, MatchOption,
|
||||
MatchStatePublic,
|
||||
},
|
||||
app::{AppPublic, AppPublicInner, Delta, MatchOption, MatchStatePublic},
|
||||
lib::interface::musicbrainz::Match,
|
||||
testmod::COLLECTION,
|
||||
tests::terminal,
|
||||
@ -240,20 +235,10 @@ mod tests {
|
||||
info: m.info,
|
||||
state: m.state,
|
||||
}),
|
||||
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),
|
||||
},
|
||||
input: self.input,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -286,6 +271,7 @@ mod tests {
|
||||
let mut app = AppPublic {
|
||||
inner: public_inner(collection, selection),
|
||||
state: AppState::Browse(()),
|
||||
input: None,
|
||||
};
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
|
||||
@ -364,6 +350,7 @@ mod tests {
|
||||
info: None,
|
||||
state: &mut widget_state,
|
||||
}),
|
||||
input: None,
|
||||
};
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
}
|
||||
@ -393,6 +380,7 @@ mod tests {
|
||||
info: Some(&artist_matches),
|
||||
state: &mut widget_state,
|
||||
}),
|
||||
input: None,
|
||||
};
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
}
|
||||
@ -427,6 +415,7 @@ mod tests {
|
||||
info: Some(&album_matches),
|
||||
state: &mut widget_state,
|
||||
}),
|
||||
input: None,
|
||||
};
|
||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user