Selected item is always at the bottom of list #41

Merged
wojtek merged 7 commits from 40---selected-item-is-always-at-the-bottom-of-list into main 2023-04-27 19:05:37 +02:00
Showing only changes of commit f9a9c264cc - Show all commits

View File

@ -13,44 +13,44 @@ use ratatui::{
use super::Error; use super::Error;
struct TrackSelection { struct TrackSelection {
selection: ListState, state: ListState,
} }
struct AlbumSelection { struct AlbumSelection {
selection: ListState, state: ListState,
track: TrackSelection, track: TrackSelection,
} }
struct ArtistSelection { struct ArtistSelection {
selection: ListState, state: ListState,
album: AlbumSelection, album: AlbumSelection,
} }
impl TrackSelection { impl TrackSelection {
fn initialise(tracks: Option<&[Track]>) -> Self { fn initialise(tracks: Option<&[Track]>) -> Self {
let mut selection = ListState::default(); let mut state = ListState::default();
if let Some(tracks) = tracks { if let Some(tracks) = tracks {
selection.select(if !tracks.is_empty() { Some(0) } else { None }); state.select(if !tracks.is_empty() { Some(0) } else { None });
} else { } else {
selection.select(None); state.select(None);
}; };
TrackSelection { selection } TrackSelection { state }
} }
fn increment(&mut self, tracks: &[Track]) { fn increment(&mut self, tracks: &[Track]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
if let Some(result) = index.checked_add(1) { if let Some(result) = index.checked_add(1) {
if result < tracks.len() { if result < tracks.len() {
self.selection.select(Some(result)); self.state.select(Some(result));
} }
} }
} }
} }
fn decrement(&mut self, _tracks: &[Track]) { fn decrement(&mut self, _tracks: &[Track]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
if let Some(result) = index.checked_sub(1) { if let Some(result) = index.checked_sub(1) {
self.selection.select(Some(result)); self.state.select(Some(result));
} }
} }
} }
@ -58,23 +58,23 @@ impl TrackSelection {
impl AlbumSelection { impl AlbumSelection {
fn initialise(albums: Option<&[Album]>) -> Self { fn initialise(albums: Option<&[Album]>) -> Self {
let mut selection = ListState::default(); let mut state = ListState::default();
let track: TrackSelection; let track: TrackSelection;
if let Some(albums) = albums { if let Some(albums) = albums {
selection.select(if !albums.is_empty() { Some(0) } else { None }); state.select(if !albums.is_empty() { Some(0) } else { None });
track = TrackSelection::initialise(albums.get(0).map(|a| a.tracks.as_slice())); track = TrackSelection::initialise(albums.get(0).map(|a| a.tracks.as_slice()));
} else { } else {
selection.select(None); state.select(None);
track = TrackSelection::initialise(None); track = TrackSelection::initialise(None);
} }
AlbumSelection { selection, track } AlbumSelection { state, track }
} }
fn increment(&mut self, albums: &[Album]) { fn increment(&mut self, albums: &[Album]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
if let Some(result) = index.checked_add(1) { if let Some(result) = index.checked_add(1) {
if result < albums.len() { if result < albums.len() {
self.selection.select(Some(result)); self.state.select(Some(result));
self.track = TrackSelection::initialise(Some(&albums[result].tracks)); self.track = TrackSelection::initialise(Some(&albums[result].tracks));
} }
} }
@ -82,22 +82,22 @@ impl AlbumSelection {
} }
fn increment_track(&mut self, albums: &[Album]) { fn increment_track(&mut self, albums: &[Album]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
self.track.increment(&albums[index].tracks); self.track.increment(&albums[index].tracks);
} }
} }
fn decrement(&mut self, albums: &[Album]) { fn decrement(&mut self, albums: &[Album]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
if let Some(result) = index.checked_sub(1) { if let Some(result) = index.checked_sub(1) {
self.selection.select(Some(result)); self.state.select(Some(result));
self.track = TrackSelection::initialise(Some(&albums[result].tracks)); self.track = TrackSelection::initialise(Some(&albums[result].tracks));
} }
} }
} }
fn decrement_track(&mut self, albums: &[Album]) { fn decrement_track(&mut self, albums: &[Album]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
self.track.decrement(&albums[index].tracks); self.track.decrement(&albums[index].tracks);
} }
} }
@ -105,23 +105,23 @@ impl AlbumSelection {
impl ArtistSelection { impl ArtistSelection {
fn initialise(artists: Option<&[Artist]>) -> Self { fn initialise(artists: Option<&[Artist]>) -> Self {
let mut selection = ListState::default(); let mut state = ListState::default();
let album: AlbumSelection; let album: AlbumSelection;
if let Some(artists) = artists { if let Some(artists) = artists {
selection.select(if !artists.is_empty() { Some(0) } else { None }); state.select(if !artists.is_empty() { Some(0) } else { None });
album = AlbumSelection::initialise(artists.get(0).map(|a| a.albums.as_slice())); album = AlbumSelection::initialise(artists.get(0).map(|a| a.albums.as_slice()));
} else { } else {
selection.select(None); state.select(None);
album = AlbumSelection::initialise(None); album = AlbumSelection::initialise(None);
} }
ArtistSelection { selection, album } ArtistSelection { state, album }
} }
fn increment(&mut self, artists: &[Artist]) { fn increment(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
if let Some(result) = index.checked_add(1) { if let Some(result) = index.checked_add(1) {
if result < artists.len() { if result < artists.len() {
self.selection.select(Some(result)); self.state.select(Some(result));
self.album = AlbumSelection::initialise(Some(&artists[result].albums)); self.album = AlbumSelection::initialise(Some(&artists[result].albums));
} }
} }
@ -129,34 +129,34 @@ impl ArtistSelection {
} }
fn increment_album(&mut self, artists: &[Artist]) { fn increment_album(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
self.album.increment(&artists[index].albums); self.album.increment(&artists[index].albums);
} }
} }
fn increment_track(&mut self, artists: &[Artist]) { fn increment_track(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
self.album.increment_track(&artists[index].albums); self.album.increment_track(&artists[index].albums);
} }
} }
fn decrement(&mut self, artists: &[Artist]) { fn decrement(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
if let Some(result) = index.checked_sub(1) { if let Some(result) = index.checked_sub(1) {
self.selection.select(Some(result)); self.state.select(Some(result));
self.album = AlbumSelection::initialise(Some(&artists[result].albums)); self.album = AlbumSelection::initialise(Some(&artists[result].albums));
} }
} }
} }
fn decrement_album(&mut self, artists: &[Artist]) { fn decrement_album(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
self.album.decrement(&artists[index].albums); self.album.decrement(&artists[index].albums);
} }
} }
fn decrement_track(&mut self, artists: &[Artist]) { fn decrement_track(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.state.selected() {
self.album.decrement_track(&artists[index].albums); self.album.decrement_track(&artists[index].albums);
} }
} }
@ -247,13 +247,11 @@ pub struct MhUi<CM> {
struct ArtistArea { struct ArtistArea {
list: Rect, list: Rect,
album: AlbumArea,
} }
struct AlbumArea { struct AlbumArea {
list: Rect, list: Rect,
info: Rect, info: Rect,
track: TrackArea,
} }
struct TrackArea { struct TrackArea {
@ -261,32 +259,14 @@ struct TrackArea {
info: Rect, info: Rect,
} }
struct ArtistState<'a> { struct FrameArea {
list: List<'a>, artist: ArtistArea,
album: AlbumArea,
track: TrackArea,
} }
struct AlbumState<'a> { impl FrameArea {
list: List<'a>, fn new(frame: Rect) -> FrameArea {
info: Paragraph<'a>,
}
struct TrackState<'a> {
list: List<'a>,
info: Paragraph<'a>,
}
impl<CM: CollectionManager> MhUi<CM> {
pub fn new(mut collection_manager: CM) -> Result<Self, Error> {
collection_manager.rescan_library()?;
let selection = Selection::new(Some(collection_manager.get_collection()));
Ok(MhUi {
collection_manager,
selection,
running: true,
})
}
fn construct_areas(frame: Rect) -> ArtistArea {
let width_one_third = frame.width / 3; let width_one_third = frame.width / 3;
let height_one_third = frame.height / 3; let height_one_third = frame.height / 3;
@ -330,20 +310,28 @@ impl<CM: CollectionManager> MhUi<CM> {
height: panel_height_bottom, height: panel_height_bottom,
}; };
ArtistArea { FrameArea {
list: artist_list, artist: ArtistArea { list: artist_list },
album: AlbumArea { album: AlbumArea {
list: album_list, list: album_list,
info: album_info, info: album_info,
track: TrackArea { },
list: track_list, track: TrackArea {
info: track_info, list: track_list,
}, info: track_info,
}, },
} }
} }
}
fn construct_artist_state(artists: &[Artist]) -> ArtistState { struct ArtistState<'a, 'b> {
active: bool,
list: List<'a>,
state: &'b mut ListState,
}
impl<'a, 'b> ArtistState<'a, 'b> {
fn new(active: bool, artists: &'a [Artist], state: &'b mut ListState) -> ArtistState<'a, 'b> {
let list = List::new( let list = List::new(
artists artists
.iter() .iter()
@ -351,10 +339,23 @@ impl<CM: CollectionManager> MhUi<CM> {
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
ArtistState { list } ArtistState {
active,
list,
state,
}
} }
}
fn construct_album_state(albums: &[Album], selected: Option<usize>) -> AlbumState { struct AlbumState<'a, 'b> {
active: bool,
list: List<'a>,
state: &'b mut ListState,
info: Paragraph<'a>,
}
impl<'a, 'b> AlbumState<'a, 'b> {
fn new(active: bool, albums: &'a [Album], state: &'b mut ListState) -> AlbumState<'a, 'b> {
let list = List::new( let list = List::new(
albums albums
.iter() .iter()
@ -362,7 +363,7 @@ impl<CM: CollectionManager> MhUi<CM> {
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
let album = selected.map(|i| &albums[i]); let album = state.selected().map(|i| &albums[i]);
let info = Paragraph::new(format!( let info = Paragraph::new(format!(
"Title: {}\n\ "Title: {}\n\
Year: {}", Year: {}",
@ -372,10 +373,24 @@ impl<CM: CollectionManager> MhUi<CM> {
.unwrap_or_else(|| "".to_string()), .unwrap_or_else(|| "".to_string()),
)); ));
AlbumState { list, info } AlbumState {
active,
list,
state,
info,
}
} }
}
fn construct_track_state(tracks: &[Track], selected: Option<usize>) -> TrackState { struct TrackState<'a, 'b> {
active: bool,
list: List<'a>,
state: &'b mut ListState,
info: Paragraph<'a>,
}
impl<'a, 'b> TrackState<'a, 'b> {
fn new(active: bool, tracks: &'a [Track], state: &'b mut ListState) -> TrackState<'a, 'b> {
let list = List::new( let list = List::new(
tracks tracks
.iter() .iter()
@ -383,12 +398,12 @@ impl<CM: CollectionManager> MhUi<CM> {
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
let track = selected.map(|i| &tracks[i]); let track = state.selected().map(|i| &tracks[i]);
let info = Paragraph::new(format!( let info = Paragraph::new(format!(
"Track: {}\n\ "Track: {}\n\
Title: {}\n\ Title: {}\n\
Artist: {}\n\ Artist: {}\n\
Format: {}", Format: {}",
track track
.map(|t| t.number.to_string()) .map(|t| t.number.to_string())
.unwrap_or_else(|| "".to_string()), .unwrap_or_else(|| "".to_string()),
@ -404,7 +419,24 @@ impl<CM: CollectionManager> MhUi<CM> {
.unwrap_or(""), .unwrap_or(""),
)); ));
TrackState { list, info } TrackState {
active,
list,
state,
info,
}
}
}
impl<CM: CollectionManager> MhUi<CM> {
pub fn new(mut collection_manager: CM) -> Result<Self, Error> {
collection_manager.rescan_library()?;
let selection = Selection::new(Some(collection_manager.get_collection()));
Ok(MhUi {
collection_manager,
selection,
running: true,
})
} }
fn style(_active: bool) -> Style { fn style(_active: bool) -> Style {
@ -465,98 +497,18 @@ impl<CM: CollectionManager> MhUi<CM> {
); );
} }
fn render_artist_column<B: Backend>( fn render_artist_column<B: Backend>(st: ArtistState, ar: ArtistArea, fr: &mut Frame<'_, B>) {
artists: &[Artist], Self::render_list_widget("Artists", st.list, st.state, st.active, ar.list, fr);
category: Category,
selection: &mut ArtistSelection,
area: ArtistArea,
frame: &mut Frame<'_, B>,
) {
let state = Self::construct_artist_state(artists);
Self::render_list_widget(
"Artists",
state.list,
&mut selection.selection,
category == Category::Artist,
area.list,
frame,
);
let empty_vec: Vec<Album> = vec![];
Self::render_album_column(
if let Some(artist_index) = selection.selection.selected() {
&artists[artist_index].albums
} else {
&empty_vec
},
category,
&mut selection.album,
area.album,
frame,
);
} }
fn render_album_column<B: Backend>( fn render_album_column<B: Backend>(st: AlbumState, ar: AlbumArea, fr: &mut Frame<'_, B>) {
albums: &[Album], Self::render_list_widget("Albums", st.list, st.state, st.active, ar.list, fr);
category: Category, Self::render_info_widget("Album info", st.info, st.active, ar.info, fr);
selection: &mut AlbumSelection,
area: AlbumArea,
frame: &mut Frame<'_, B>,
) {
let state = Self::construct_album_state(albums, selection.selection.selected());
Self::render_list_widget(
"Albums",
state.list,
&mut selection.selection,
category == Category::Album,
area.list,
frame,
);
Self::render_info_widget(
"Album info",
state.info,
category == Category::Album,
area.info,
frame,
);
let empty_vec: Vec<Track> = vec![];
Self::render_track_column(
if let Some(album_index) = selection.selection.selected() {
&albums[album_index].tracks
} else {
&empty_vec
},
category,
&mut selection.track,
area.track,
frame,
);
} }
fn render_track_column<B: Backend>( fn render_track_column<B: Backend>(st: TrackState, ar: TrackArea, fr: &mut Frame<'_, B>) {
tracks: &[Track], Self::render_list_widget("Tracks", st.list, st.state, st.active, ar.list, fr);
category: Category, Self::render_info_widget("Track info", st.info, st.active, ar.info, fr);
selection: &mut TrackSelection,
area: TrackArea,
frame: &mut Frame<'_, B>,
) {
let state = Self::construct_track_state(tracks, selection.selection.selected());
Self::render_list_widget(
"Tracks",
state.list,
&mut selection.selection,
category == Category::Track,
area.list,
frame,
);
Self::render_info_widget(
"Track info",
state.info,
category == Category::Track,
area.info,
frame,
);
} }
} }
@ -601,15 +553,48 @@ impl<CM: CollectionManager> Ui for MhUi<CM> {
} }
fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>) { fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>) {
let areas = Self::construct_areas(frame.size()); let active = self.selection.active;
let areas = FrameArea::new(frame.size());
Self::render_artist_column( let artists = self.collection_manager.get_collection();
self.collection_manager.get_collection(), let artist_selection = &mut self.selection.artist;
self.selection.active, let artist_state = ArtistState::new(
&mut self.selection.artist, active == Category::Artist,
areas, artists,
frame, &mut artist_selection.state,
); );
Self::render_artist_column(artist_state, areas.artist, frame);
let no_albums: Vec<Album> = vec![];
let albums = artist_selection
.state
.selected()
.map(|i| &artists[i].albums)
.unwrap_or_else(|| &no_albums);
let album_selection = &mut artist_selection.album;
let album_state = AlbumState::new(
active == Category::Album,
albums,
&mut album_selection.state,
);
Self::render_album_column(album_state, areas.album, frame);
let no_tracks: Vec<Track> = vec![];
let tracks = album_selection
.state
.selected()
.map(|i| &albums[i].tracks)
.unwrap_or_else(|| &no_tracks);
let track_selection = &mut album_selection.track;
let track_state = TrackState::new(
active == Category::Track,
tracks,
&mut track_selection.state,
);
Self::render_track_column(track_state, areas.track, frame);
} }
} }