Add manual input elements to the app an ui #216

Merged
wojtek merged 17 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-15 15:20:11 +02:00
12 changed files with 116 additions and 108 deletions
Showing only changes of commit e4c77c982d - Show all commits

View File

@ -1,17 +1,14 @@
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
selection::{Delta, ListSelection}, selection::{Delta, ListSelection},
AppPublic, AppState, IAppInteractBrowse, AppPublicState, AppState, IAppInteractBrowse,
}; };
pub struct BrowseState; pub struct BrowseState;
impl AppMachine<BrowseState> { impl AppMachine<BrowseState> {
pub fn browse_state(inner: AppInner) -> Self { pub fn browse_state(inner: AppInner) -> Self {
AppMachine { AppMachine::new(inner, BrowseState)
inner,
state: BrowseState,
}
} }
} }
@ -21,12 +18,9 @@ impl From<AppMachine<BrowseState>> for App {
} }
} }
impl<'a> From<&'a mut AppMachine<BrowseState>> for AppPublic<'a> { impl<'a> From<&'a mut BrowseState> for AppPublicState<'a> {
fn from(machine: &'a mut AppMachine<BrowseState>) -> Self { fn from(_state: &'a mut BrowseState) -> Self {
AppPublic { AppState::Browse(())
inner: (&mut machine.inner).into(),
state: AppState::Browse(()),
}
} }
} }

View File

@ -1,20 +1,23 @@
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
AppPublic, AppState, AppPublicState, AppState,
}; };
pub struct CriticalState { pub struct CriticalState {
string: String, string: String,
} }
impl CriticalState {
fn new<S: Into<String>>(string: S) -> Self {
CriticalState {
string: string.into(),
}
}
}
impl AppMachine<CriticalState> { impl AppMachine<CriticalState> {
pub fn critical_state<S: Into<String>>(inner: AppInner, string: S) -> Self { pub fn critical_state<S: Into<String>>(inner: AppInner, string: S) -> Self {
AppMachine { AppMachine::new(inner, CriticalState::new(string))
inner,
state: CriticalState {
string: string.into(),
},
}
} }
} }
@ -24,11 +27,8 @@ impl From<AppMachine<CriticalState>> for App {
} }
} }
impl<'a> From<&'a mut AppMachine<CriticalState>> for AppPublic<'a> { impl<'a> From<&'a mut CriticalState> for AppPublicState<'a> {
fn from(machine: &'a mut AppMachine<CriticalState>) -> Self { fn from(state: &'a mut CriticalState) -> Self {
AppPublic { AppState::Critical(&state.string)
inner: (&mut machine.inner).into(),
state: AppState::Critical(&machine.state.string),
}
} }
} }

View File

@ -1,20 +1,23 @@
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
AppPublic, AppState, IAppInteractError, AppPublicState, AppState, IAppInteractError,
}; };
pub struct ErrorState { pub struct ErrorState {
string: String, string: String,
} }
impl ErrorState {
fn new<S: Into<String>>(string: S) -> Self {
ErrorState {
string: string.into(),
}
}
}
impl AppMachine<ErrorState> { impl AppMachine<ErrorState> {
pub fn error_state<S: Into<String>>(inner: AppInner, string: S) -> Self { pub fn error_state<S: Into<String>>(inner: AppInner, string: S) -> Self {
AppMachine { AppMachine::new(inner, ErrorState::new(string))
inner,
state: ErrorState {
string: string.into(),
},
}
} }
} }
@ -24,12 +27,9 @@ impl From<AppMachine<ErrorState>> for App {
} }
} }
impl<'a> From<&'a mut AppMachine<ErrorState>> for AppPublic<'a> { impl<'a> From<&'a mut ErrorState> for AppPublicState<'a> {
fn from(machine: &'a mut AppMachine<ErrorState>) -> Self { fn from(state: &'a mut ErrorState) -> Self {
AppPublic { AppState::Error(&state.string)
inner: (&mut machine.inner).into(),
state: AppState::Error(&machine.state.string),
}
} }
} }

View File

@ -15,7 +15,7 @@ use musichoard::collection::{
use crate::tui::{ use crate::tui::{
app::{ app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
AppPublic, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo, AppPublicState, AppState, IAppEventFetch, IAppInteractFetch, MatchStateInfo,
}, },
event::{Event, EventSender}, event::{Event, EventSender},
lib::interface::musicbrainz::{self, Error as MbError, IMusicBrainz}, lib::interface::musicbrainz::{self, Error as MbError, IMusicBrainz},
@ -40,7 +40,7 @@ pub type FetchReceiver = mpsc::Receiver<FetchResult>;
impl AppMachine<FetchState> { impl AppMachine<FetchState> {
fn fetch_state(inner: AppInner, state: FetchState) -> Self { fn fetch_state(inner: AppInner, state: FetchState) -> Self {
AppMachine { inner, state } AppMachine::new(inner, state)
} }
pub fn app_fetch_new(inner: AppInner) -> App { pub fn app_fetch_new(inner: AppInner) -> App {
@ -168,12 +168,9 @@ impl From<AppMachine<FetchState>> for App {
} }
} }
impl<'a> From<&'a mut AppMachine<FetchState>> for AppPublic<'a> { impl<'a> From<&'a mut FetchState> for AppPublicState<'a> {
fn from(machine: &'a mut AppMachine<FetchState>) -> Self { fn from(_state: &'a mut FetchState) -> Self {
AppPublic { AppState::Fetch(())
inner: (&mut machine.inner).into(),
state: AppState::Fetch(()),
}
} }
} }

View File

@ -1,16 +1,13 @@
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
AppPublic, AppState, IAppInteractInfo, AppPublicState, AppState, IAppInteractInfo,
}; };
pub struct InfoState; pub struct InfoState;
impl AppMachine<InfoState> { impl AppMachine<InfoState> {
pub fn info_state(inner: AppInner) -> Self { pub fn info_state(inner: AppInner) -> Self {
AppMachine { AppMachine::new(inner, InfoState)
inner,
state: InfoState,
}
} }
} }
@ -20,12 +17,9 @@ impl From<AppMachine<InfoState>> for App {
} }
} }
impl<'a> From<&'a mut AppMachine<InfoState>> for AppPublic<'a> { impl<'a> From<&'a mut InfoState> for AppPublicState<'a> {
fn from(machine: &'a mut AppMachine<InfoState>) -> Self { fn from(_state: &'a mut InfoState) -> Self {
AppPublic { AppState::Info(())
inner: (&mut machine.inner).into(),
state: AppState::Info(()),
}
} }
} }

View File

@ -20,7 +20,7 @@ impl IAppInput for AppInputMode {
self.input self.input
.0 .0
.handle_event(&crossterm::event::Event::Key(input)); .handle_event(&crossterm::event::Event::Key(input));
self.app.inner_mut().input.replace(self.input); self.app.input_mut().replace(self.input);
self.app self.app
} }

View File

@ -2,7 +2,7 @@ use std::cmp;
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
AlbumMatches, AppPublic, AppState, ArtistMatches, IAppInteractMatch, MatchOption, AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, MatchOption,
MatchStateInfo, MatchStatePublic, WidgetState, MatchStateInfo, MatchStatePublic, WidgetState,
}; };
@ -98,7 +98,7 @@ impl MatchState {
impl AppMachine<MatchState> { impl AppMachine<MatchState> {
pub fn match_state(inner: AppInner, state: MatchState) -> Self { pub fn match_state(inner: AppInner, state: MatchState) -> Self {
AppMachine { inner, state } AppMachine::new(inner, state)
} }
pub fn submit_input(&mut self, _input: Input) {} pub fn submit_input(&mut self, _input: Input) {}
@ -110,21 +110,12 @@ impl From<AppMachine<MatchState>> for App {
} }
} }
impl<'a> From<&'a mut MatchState> for MatchStatePublic<'a> { impl<'a> From<&'a mut MatchState> for AppPublicState<'a> {
fn from(state: &'a mut MatchState) -> Self { fn from(state: &'a mut MatchState) -> Self {
MatchStatePublic { AppState::Match(MatchStatePublic {
info: state.current.as_ref().map(Into::into), info: state.current.as_ref().map(Into::into),
state: &mut state.state, state: &mut state.state,
} })
}
}
impl<'a> From<&'a mut AppMachine<MatchState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<MatchState>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: AppState::Match((&mut machine.state).into()),
}
} }
} }
@ -164,7 +155,7 @@ impl IAppInteractMatch for AppMachine<MatchState> {
.unwrap() .unwrap()
.is_manual_input_mbid(index) .is_manual_input_mbid(index)
{ {
self.inner.input = Some(Input::default()); self.input.replace(Input::default());
return self.into(); return self.into();
} }
} }

View File

@ -26,7 +26,7 @@ use match_state::MatchState;
use reload_state::ReloadState; use reload_state::ReloadState;
use search_state::SearchState; use search_state::SearchState;
use super::{AppMode, IAppBase, IAppState}; use super::{AppMode, AppPublicState, IAppBase, IAppState};
pub type App = AppState< pub type App = AppState<
AppMachine<BrowseState>, AppMachine<BrowseState>,
@ -42,6 +42,7 @@ pub type App = AppState<
pub struct AppMachine<STATE> { pub struct AppMachine<STATE> {
inner: AppInner, inner: AppInner,
state: STATE, state: STATE,
input: Option<Input>,
} }
pub struct AppInner { pub struct AppInner {
@ -50,7 +51,6 @@ pub struct AppInner {
musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>, musicbrainz: Arc<Mutex<dyn IMusicBrainz + Send>>,
selection: Selection, selection: Selection,
events: EventSender, events: EventSender,
input: Option<Input>,
} }
pub struct AppInputMode { pub struct AppInputMode {
@ -108,6 +108,19 @@ impl App {
AppState::Critical(critical_state) => &mut critical_state.inner, AppState::Critical(critical_state) => &mut critical_state.inner,
} }
} }
fn input_mut(&mut self) -> &mut Option<Input> {
match self {
AppState::Browse(state) => &mut state.input,
AppState::Info(state) => &mut state.input,
AppState::Reload(state) => &mut state.input,
AppState::Search(state) => &mut state.input,
AppState::Fetch(state) => &mut state.input,
AppState::Match(state) => &mut state.input,
AppState::Error(state) => &mut state.input,
AppState::Critical(state) => &mut state.input,
}
}
} }
impl IApp for App { impl IApp for App {
@ -135,7 +148,7 @@ impl IApp for App {
} }
fn mode(mut self) -> super::AppMode<IAppState!(), Self::InputMode> { fn mode(mut self) -> super::AppMode<IAppState!(), Self::InputMode> {
if let Some(input) = self.inner_mut().input.take() { if let Some(input) = self.input_mut().take() {
AppMode::Input(AppInputMode::new(input, self.state())) AppMode::Input(AppInputMode::new(input, self.state()))
} else { } else {
AppMode::State(self.state()) AppMode::State(self.state())
@ -179,7 +192,6 @@ impl AppInner {
musicbrainz: Arc::new(Mutex::new(musicbrainz)), musicbrainz: Arc::new(Mutex::new(musicbrainz)),
selection, selection,
events, events,
input: None,
} }
} }
} }
@ -189,7 +201,29 @@ impl<'a> From<&'a mut AppInner> for AppPublicInner<'a> {
AppPublicInner { AppPublicInner {
collection: inner.music_hoard.get_collection(), collection: inner.music_hoard.get_collection(),
selection: &mut inner.selection, selection: &mut inner.selection,
input: inner.input.as_ref().map(Into::into), }
}
}
impl<State> AppMachine<State> {
pub fn new(inner: AppInner, state: State) -> Self {
AppMachine {
inner,
state,
input: None,
}
}
}
impl<'a, State> From<&'a mut AppMachine<State>> for AppPublic<'a>
where
&'a mut State: Into<AppPublicState<'a>>,
{
fn from(machine: &'a mut AppMachine<State>) -> Self {
AppPublic {
inner: (&mut machine.inner).into(),
state: (&mut machine.state).into(),
input: machine.input.as_ref().map(Into::into),
} }
} }
} }
@ -418,7 +452,7 @@ mod tests {
let (_, rx) = mpsc::channel(); let (_, rx) = mpsc::channel();
let inner = app.unwrap_browse().inner; let inner = app.unwrap_browse().inner;
let state = FetchState::new(rx); let state = FetchState::new(rx);
app = AppMachine { inner, state }.into(); app = AppMachine::new(inner, state).into();
let state = app.state(); let state = app.state();
assert!(matches!(state, AppState::Fetch(_))); assert!(matches!(state, AppState::Fetch(_)));

View File

@ -1,17 +1,14 @@
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
selection::KeySelection, selection::KeySelection,
AppPublic, AppState, IAppInteractReload, AppPublicState, AppState, IAppInteractReload,
}; };
pub struct ReloadState; pub struct ReloadState;
impl AppMachine<ReloadState> { impl AppMachine<ReloadState> {
pub fn reload_state(inner: AppInner) -> Self { pub fn reload_state(inner: AppInner) -> Self {
AppMachine { AppMachine::new(inner, ReloadState)
inner,
state: ReloadState,
}
} }
} }
@ -20,12 +17,10 @@ impl From<AppMachine<ReloadState>> for App {
AppState::Reload(machine) AppState::Reload(machine)
} }
} }
impl<'a> From<&'a mut AppMachine<ReloadState>> for AppPublic<'a> {
fn from(machine: &'a mut AppMachine<ReloadState>) -> Self { impl<'a> From<&'a mut ReloadState> for AppPublicState<'a> {
AppPublic { fn from(_state: &'a mut ReloadState) -> Self {
inner: (&mut machine.inner).into(), AppState::Reload(())
state: AppState::Reload(()),
}
} }
} }

View File

@ -6,7 +6,7 @@ use musichoard::collection::{album::Album, artist::Artist, track::Track};
use crate::tui::app::{ use crate::tui::app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
selection::{ListSelection, SelectionState}, selection::{ListSelection, SelectionState},
AppPublic, AppState, Category, IAppInteractSearch, AppPublicState, AppState, Category, IAppInteractSearch,
}; };
// Unlikely that this covers all possible strings, but it should at least cover strings // Unlikely that this covers all possible strings, but it should at least cover strings
@ -31,16 +31,19 @@ struct SearchStateMemo {
char: bool, char: bool,
} }
impl SearchState {
fn new(orig: ListSelection) -> Self {
SearchState {
string: String::new(),
orig,
memo: vec![],
}
}
}
impl AppMachine<SearchState> { impl AppMachine<SearchState> {
pub fn search_state(inner: AppInner, orig: ListSelection) -> Self { pub fn search_state(inner: AppInner, orig: ListSelection) -> Self {
AppMachine { AppMachine::new(inner, SearchState::new(orig))
inner,
state: SearchState {
string: String::new(),
orig,
memo: vec![],
},
}
} }
} }
@ -50,12 +53,9 @@ impl From<AppMachine<SearchState>> for App {
} }
} }
impl<'a> From<&'a mut AppMachine<SearchState>> for AppPublic<'a> { impl<'a> From<&'a mut SearchState> for AppPublicState<'a> {
fn from(machine: &'a mut AppMachine<SearchState>) -> Self { fn from(state: &'a mut SearchState) -> Self {
AppPublic { AppState::Search(&state.string)
inner: (&mut machine.inner).into(),
state: AppState::Search(&machine.state.string),
}
} }
} }

View File

@ -151,12 +151,12 @@ pub trait IAppAccess {
pub struct AppPublic<'app> { pub struct AppPublic<'app> {
pub inner: AppPublicInner<'app>, pub inner: AppPublicInner<'app>,
pub state: AppPublicState<'app>, pub state: AppPublicState<'app>,
pub input: Option<InputPublic<'app>>,
} }
pub struct AppPublicInner<'app> { pub struct AppPublicInner<'app> {
pub collection: &'app Collection, pub collection: &'app Collection,
pub selection: &'app mut Selection, pub selection: &'app mut Selection,
pub input: Option<InputPublic<'app>>,
} }
pub type InputPublic<'app> = &'app tui_input::Input; pub type InputPublic<'app> = &'app tui_input::Input;

View File

@ -191,7 +191,7 @@ impl IUi for Ui {
_ => {} _ => {}
} }
if let Some(input) = app.inner.input { if let Some(input) = app.input {
Self::render_input_overlay(input, frame); Self::render_input_overlay(input, frame);
} }
} }
@ -220,7 +220,6 @@ mod tests {
inner: AppPublicInner { inner: AppPublicInner {
collection: self.inner.collection, collection: self.inner.collection,
selection: self.inner.selection, selection: self.inner.selection,
input: self.inner.input,
}, },
state: match self.state { state: match self.state {
AppState::Browse(()) => AppState::Browse(()), AppState::Browse(()) => AppState::Browse(()),
@ -235,6 +234,7 @@ mod tests {
AppState::Error(s) => AppState::Error(s), AppState::Error(s) => AppState::Error(s),
AppState::Critical(s) => AppState::Critical(s), AppState::Critical(s) => AppState::Critical(s),
}, },
input: self.input,
} }
} }
} }
@ -246,7 +246,6 @@ mod tests {
AppPublicInner { AppPublicInner {
collection, collection,
selection, selection,
input: None,
} }
} }
@ -268,6 +267,7 @@ mod tests {
let mut app = AppPublic { let mut app = AppPublic {
inner: public_inner(collection, selection), inner: public_inner(collection, selection),
state: AppState::Browse(()), state: AppState::Browse(()),
input: None,
}; };
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
@ -346,6 +346,7 @@ mod tests {
info: None, info: None,
state: &mut widget_state, state: &mut widget_state,
}), }),
input: None,
}; };
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }
@ -375,6 +376,7 @@ mod tests {
info: Some(&artist_matches), info: Some(&artist_matches),
state: &mut widget_state, state: &mut widget_state,
}), }),
input: None,
}; };
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }
@ -409,6 +411,7 @@ mod tests {
info: Some(&album_matches), info: Some(&album_matches),
state: &mut widget_state, state: &mut widget_state,
}), }),
input: None,
}; };
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
} }