Paging of match page

This commit is contained in:
Wojciech Kozlowski 2024-09-29 21:17:17 +02:00
parent 77a97659d1
commit 66b273bef4
8 changed files with 79 additions and 69 deletions

View File

@ -1,7 +1,7 @@
use crate::tui::app::{
machine::{App, AppInner, AppMachine},
selection::{Delta, ListSelection},
AppPublicState, AppState, IAppInteractBrowse,
selection::ListSelection,
AppPublicState, AppState, IAppInteractBrowse, Delta
};
pub struct BrowseState;

View File

@ -9,8 +9,8 @@ use musichoard::collection::{
use crate::tui::{
app::{
machine::{fetch_state::FetchState, input::Input, App, AppInner, AppMachine},
AlbumMatches, AppPublicState, AppState, ArtistMatches, IAppInteractMatch, ListOption,
MatchOption, MatchStateInfo, MatchStatePublic, WidgetState,
AlbumMatches, AppPublicState, AppState, ArtistMatches, Delta, IAppInteractMatch,
ListOption, MatchOption, MatchStateInfo, MatchStatePublic, WidgetState,
},
lib::interface::musicbrainz::api::{Lookup, Match},
};
@ -243,19 +243,19 @@ impl<'a> From<&'a mut MatchState> for AppPublicState<'a> {
impl IAppInteractMatch for AppMachine<MatchState> {
type APP = App;
fn prev_match(mut self) -> Self::APP {
fn decrement_match(mut self, delta: Delta) -> Self::APP {
if let Some(index) = self.state.state.list.selected() {
let result = index.saturating_sub(1);
let result = index.saturating_sub(delta.as_usize(&self.state.state));
self.state.state.list.select(Some(result));
}
self.into()
}
fn next_match(mut self) -> Self::APP {
fn increment_match(mut self, delta: Delta) -> Self::APP {
let index = self.state.state.list.selected().unwrap();
let to = cmp::min(
index.saturating_add(1),
index.saturating_add(delta.as_usize(&self.state.state)),
self.state.current.len().saturating_sub(1),
);
self.state.state.list.select(Some(to));
@ -473,38 +473,38 @@ mod tests {
assert_eq!(matches.state.current, matches_info);
assert_eq!(matches.state.state, widget_state);
let matches = matches.prev_match().unwrap_match();
let matches = matches.decrement_match(Delta::Line).unwrap_match();
assert_eq!(matches.state.current, matches_info);
assert_eq!(matches.state.state.list.selected(), Some(0));
let mut matches = matches;
for ii in 1..len {
matches = matches.next_match().unwrap_match();
matches = matches.increment_match(Delta::Line).unwrap_match();
assert_eq!(matches.state.current, matches_info);
assert_eq!(matches.state.state.list.selected(), Some(ii));
}
// Next is CannotHaveMBID
let matches = matches.next_match().unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
assert_eq!(matches.state.current, matches_info);
assert_eq!(matches.state.state.list.selected(), Some(len));
// Next is ManualInputMbid
let matches = matches.next_match().unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
assert_eq!(matches.state.current, matches_info);
assert_eq!(matches.state.state.list.selected(), Some(len + 1));
let matches = matches.next_match().unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
assert_eq!(matches.state.current, matches_info);
assert_eq!(matches.state.state.list.selected(), Some(len + 1));
// Go prev_match first as selecting on manual input does not go back to fetch.
let matches = matches.prev_match().unwrap_match();
let matches = matches.decrement_match(Delta::Line).unwrap_match();
matches.select().unwrap_fetch();
}
@ -619,10 +619,10 @@ mod tests {
AppMachine::match_state(inner(music_hoard(vec![])), match_state(album_match()));
// album_match has two matches which means that the fourth option should be manual input.
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let app = matches.select();
@ -657,8 +657,8 @@ mod tests {
);
// There are no matches which means that the second option should be manual input.
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let mut app = matches.select();
app = input_mbid(app);
@ -691,8 +691,8 @@ mod tests {
);
// There are no matches which means that the second option should be manual input.
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let matches = matches.increment_match(Delta::Line).unwrap_match();
let mut app = matches.select();
app = input_mbid(app);

View File

@ -2,7 +2,8 @@ mod machine;
mod selection;
pub use machine::App;
pub use selection::{Category, Delta, Selection, WidgetState};
use ratatui::widgets::ListState;
pub use selection::{Category, Selection};
use musichoard::collection::{
album::AlbumMeta,
@ -124,8 +125,8 @@ pub trait IAppEventFetch {
pub trait IAppInteractMatch {
type APP: IApp;
fn prev_match(self) -> Self::APP;
fn next_match(self) -> Self::APP;
fn decrement_match(self, delta: Delta) -> Self::APP;
fn increment_match(self, delta: Delta) -> Self::APP;
fn select(self) -> Self::APP;
fn abort(self) -> Self::APP;
@ -159,6 +160,40 @@ pub trait IAppInteractError {
fn dismiss_error(self) -> Self::APP;
}
#[derive(Clone, Debug, Default)]
pub struct WidgetState {
pub list: ListState,
pub height: usize,
}
impl PartialEq for WidgetState {
fn eq(&self, other: &Self) -> bool {
self.list.selected().eq(&other.list.selected()) && self.height.eq(&other.height)
}
}
impl WidgetState {
#[must_use]
pub const fn with_selected(mut self, selected: Option<usize>) -> Self {
self.list = self.list.with_selected(selected);
self
}
}
pub enum Delta {
Line,
Page,
}
impl Delta {
fn as_usize(&self, state: &WidgetState) -> usize {
match self {
Delta::Line => 1,
Delta::Page => state.height.saturating_sub(1),
}
}
}
// It would be preferable to have a getter for each field separately. However, the selection field
// needs to be mutably accessible requiring a mutable borrow of the entire struct if behind a trait.
// This in turn complicates simultaneous field access since only a single mutable borrow is allowed.

View File

@ -5,9 +5,11 @@ use musichoard::collection::{
track::Track,
};
use crate::tui::app::selection::{
track::{KeySelectTrack, TrackSelection},
Delta, SelectionState, WidgetState,
use crate::tui::app::{
selection::{
track::{KeySelectTrack, TrackSelection},
SelectionState,
}, Delta, WidgetState
};
#[derive(Clone, Debug, PartialEq)]

View File

@ -6,9 +6,11 @@ use musichoard::collection::{
track::Track,
};
use crate::tui::app::selection::{
album::{AlbumSelection, KeySelectAlbum},
Delta, SelectionState, WidgetState,
use crate::tui::app::{
selection::{
album::{AlbumSelection, KeySelectAlbum},
SelectionState,
}, Delta, WidgetState
};
#[derive(Clone, Debug, PartialEq)]

View File

@ -5,7 +5,10 @@ mod track;
use musichoard::collection::{album::Album, artist::Artist, track::Track, Collection};
use ratatui::widgets::ListState;
use artist::{ArtistSelection, KeySelectArtist};
use crate::tui::app::{
selection::artist::{ArtistSelection, KeySelectArtist},
Delta, WidgetState,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Category {
@ -24,40 +27,6 @@ pub struct SelectionState<'a, T> {
pub index: usize,
}
#[derive(Clone, Debug, Default)]
pub struct WidgetState {
pub list: ListState,
pub height: usize,
}
impl PartialEq for WidgetState {
fn eq(&self, other: &Self) -> bool {
self.list.selected().eq(&other.list.selected()) && self.height.eq(&other.height)
}
}
impl WidgetState {
#[must_use]
pub const fn with_selected(mut self, selected: Option<usize>) -> Self {
self.list = self.list.with_selected(selected);
self
}
}
pub enum Delta {
Line,
Page,
}
impl Delta {
fn as_usize(&self, state: &WidgetState) -> usize {
match self {
Delta::Line => 1,
Delta::Page => state.height.saturating_sub(1),
}
}
}
impl Selection {
pub fn new(artists: &[Artist]) -> Self {
Selection {

View File

@ -2,7 +2,7 @@ use std::cmp;
use musichoard::collection::track::{Track, TrackId, TrackNum};
use crate::tui::app::selection::{Delta, SelectionState, WidgetState};
use crate::tui::app::{selection::SelectionState, Delta, WidgetState};
#[derive(Clone, Debug, PartialEq)]
pub struct TrackSelection {

View File

@ -212,8 +212,10 @@ impl<APP: IApp> IEventHandlerPrivate<APP> for EventHandler {
// Abort.
KeyCode::Esc | KeyCode::Char('q') | KeyCode::Char('Q') => app.abort(),
// Select.
KeyCode::Up => app.prev_match(),
KeyCode::Down => app.next_match(),
KeyCode::Up => app.decrement_match(Delta::Line),
KeyCode::Down => app.increment_match(Delta::Line),
KeyCode::PageUp => app.decrement_match(Delta::Page),
KeyCode::PageDown => app.increment_match(Delta::Page),
KeyCode::Enter => app.select(),
// Othey keys.
_ => app.no_op(),