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 4ee70bce53 - Show all commits

View File

@ -1,6 +1,6 @@
use musichoard::{ use musichoard::{
collection::{Collection, CollectionManager}, collection::{Collection, CollectionManager},
Album, AlbumId, Artist, Track, TrackFormat, Album, Artist, Track, TrackFormat,
}; };
use ratatui::{ use ratatui::{
backend::Backend, backend::Backend,
@ -37,10 +37,6 @@ impl TrackSelection {
TrackSelection { selection } TrackSelection { selection }
} }
fn selection(&mut self) -> &mut ListState {
&mut self.selection
}
fn increment(&mut self, tracks: &[Track]) { fn increment(&mut self, tracks: &[Track]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.selection.selected() {
if let Some(result) = index.checked_add(1) { if let Some(result) = index.checked_add(1) {
@ -74,14 +70,6 @@ impl AlbumSelection {
AlbumSelection { selection, track } AlbumSelection { selection, track }
} }
fn selection(&mut self) -> &mut ListState {
&mut self.selection
}
fn track_selection(&mut self) -> &mut ListState {
self.track.selection()
}
fn increment(&mut self, albums: &[Album]) { fn increment(&mut self, albums: &[Album]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.selection.selected() {
if let Some(result) = index.checked_add(1) { if let Some(result) = index.checked_add(1) {
@ -129,18 +117,6 @@ impl ArtistSelection {
ArtistSelection { selection, album } ArtistSelection { selection, album }
} }
fn selection(&mut self) -> &mut ListState {
&mut self.selection
}
fn album_selection(&mut self) -> &mut ListState {
self.album.selection()
}
fn track_selection(&mut self) -> &mut ListState {
self.album.track_selection()
}
fn increment(&mut self, artists: &[Artist]) { fn increment(&mut self, artists: &[Artist]) {
if let Some(index) = self.selection.selected() { if let Some(index) = self.selection.selected() {
if let Some(result) = index.checked_add(1) { if let Some(result) = index.checked_add(1) {
@ -206,18 +182,6 @@ impl Selection {
} }
} }
fn artist_selection(&mut self) -> &mut ListState {
self.artist.selection()
}
fn album_selection(&mut self) -> &mut ListState {
self.artist.album_selection()
}
fn track_selection(&mut self) -> &mut ListState {
self.artist.track_selection()
}
fn increment_category(&mut self) { fn increment_category(&mut self) {
self.active = match self.active { self.active = match self.active {
Category::Artist => Category::Album, Category::Artist => Category::Album,
@ -283,11 +247,13 @@ 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 {
@ -295,32 +261,18 @@ struct TrackArea {
info: Rect, info: Rect,
} }
struct FrameAreas {
artists: ArtistArea,
albums: AlbumArea,
tracks: TrackArea,
}
struct SelectionList<'a> {
list: List<'a>,
state: &'a mut ListState,
}
struct ArtistState<'a> { struct ArtistState<'a> {
list: SelectionList<'a>, list: List<'a>,
active: bool,
} }
struct AlbumState<'a> { struct AlbumState<'a> {
list: SelectionList<'a>, list: List<'a>,
info: Paragraph<'a>, info: Paragraph<'a>,
active: bool,
} }
struct TrackState<'a> { struct TrackState<'a> {
list: SelectionList<'a>, list: List<'a>,
info: Paragraph<'a>, info: Paragraph<'a>,
active: bool,
} }
impl<CM: CollectionManager> MhUi<CM> { impl<CM: CollectionManager> MhUi<CM> {
@ -334,7 +286,7 @@ impl<CM: CollectionManager> MhUi<CM> {
}) })
} }
fn construct_areas(frame: Rect) -> FrameAreas { 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;
@ -378,21 +330,20 @@ impl<CM: CollectionManager> MhUi<CM> {
height: panel_height_bottom, height: panel_height_bottom,
}; };
FrameAreas { ArtistArea {
artists: ArtistArea { list: artist_list }, list: artist_list,
albums: AlbumArea { album: AlbumArea {
list: album_list, list: album_list,
info: album_info, info: album_info,
}, track: TrackArea {
tracks: TrackArea {
list: track_list, list: track_list,
info: track_info, info: track_info,
}, },
},
} }
} }
fn construct_artist_state(&mut self) -> ArtistState { fn construct_artist_state(artists: &[Artist]) -> ArtistState {
let artists = self.collection_manager.get_collection();
let list = List::new( let list = List::new(
artists artists
.iter() .iter()
@ -400,69 +351,31 @@ impl<CM: CollectionManager> MhUi<CM> {
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
let active = self.selection.active == Category::Artist; ArtistState { list }
let state = self.selection.artist_selection();
ArtistState {
list: SelectionList { list, state },
active,
}
} }
fn construct_album_state(&mut self) -> AlbumState { fn construct_album_state(albums: &[Album], selected: Option<usize>) -> AlbumState {
let albums: Vec<&AlbumId> =
if let Some(artist_index) = self.selection.artist.selection.selected() {
self.collection_manager.get_collection()[artist_index]
.albums
.iter()
.map(|a| &a.id)
.collect()
} else {
vec![]
};
let list = List::new( let list = List::new(
albums albums
.iter() .iter()
.map(|id| ListItem::new(id.title.as_str())) .map(|a| ListItem::new(a.id.title.as_str()))
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
let active = self.selection.active == Category::Album; let album = selected.map(|i| &albums[i]);
let state = self.selection.album_selection();
let album = state.selected().map(|i| albums[i]);
let info = Paragraph::new(format!( let info = Paragraph::new(format!(
"Title: {}\n\ "Title: {}\n\
Year: {}", Year: {}",
album.map(|a| a.title.as_str()).unwrap_or(""), album.map(|a| a.id.title.as_str()).unwrap_or(""),
album album
.map(|a| a.year.to_string()) .map(|a| a.id.year.to_string())
.unwrap_or_else(|| "".to_string()), .unwrap_or_else(|| "".to_string()),
)); ));
AlbumState { AlbumState { list, info }
list: SelectionList { list, state },
info,
active,
}
} }
fn construct_track_state(&mut self) -> TrackState { fn construct_track_state(tracks: &[Track], selected: Option<usize>) -> TrackState {
let tracks: Vec<&Track> =
if let Some(artist_index) = self.selection.artist.selection.selected() {
if let Some(album_index) = self.selection.artist.album.selection.selected() {
self.collection_manager.get_collection()[artist_index].albums[album_index]
.tracks
.iter()
.collect()
} else {
vec![]
}
} else {
vec![]
};
let list = List::new( let list = List::new(
tracks tracks
.iter() .iter()
@ -470,10 +383,7 @@ impl<CM: CollectionManager> MhUi<CM> {
.collect::<Vec<ListItem>>(), .collect::<Vec<ListItem>>(),
); );
let active = self.selection.active == Category::Track; let track = selected.map(|i| &tracks[i]);
let state = self.selection.track_selection();
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\
@ -494,11 +404,7 @@ impl<CM: CollectionManager> MhUi<CM> {
.unwrap_or(""), .unwrap_or(""),
)); ));
TrackState { TrackState { list, info }
list: SelectionList { list, state },
info,
active,
}
} }
fn style(_active: bool) -> Style { fn style(_active: bool) -> Style {
@ -528,19 +434,19 @@ impl<CM: CollectionManager> MhUi<CM> {
fn render_list_widget<B: Backend>( fn render_list_widget<B: Backend>(
title: &str, title: &str,
mut list: SelectionList, list: List,
list_state: &mut ListState,
active: bool, active: bool,
area: Rect, area: Rect,
frame: &mut Frame<'_, B>, frame: &mut Frame<'_, B>,
) { ) {
frame.render_stateful_widget( frame.render_stateful_widget(
list.list list.highlight_style(Self::highlight_style(active))
.highlight_style(Self::highlight_style(active))
.highlight_symbol(">> ") .highlight_symbol(">> ")
.style(Self::style(active)) .style(Self::style(active))
.block(Self::block(title, active)), .block(Self::block(title, active)),
area, area,
&mut list.state, list_state,
); );
} }
@ -559,21 +465,98 @@ impl<CM: CollectionManager> MhUi<CM> {
); );
} }
fn render_artist_column<B: Backend>(&mut self, area: ArtistArea, frame: &mut Frame<'_, B>) { fn render_artist_column<B: Backend>(
let state = self.construct_artist_state(); artists: &[Artist],
Self::render_list_widget("Artists", state.list, state.active, area.list, frame); 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>(&mut self, area: AlbumArea, frame: &mut Frame<'_, B>) { fn render_album_column<B: Backend>(
let state = self.construct_album_state(); albums: &[Album],
Self::render_list_widget("Albums", state.list, state.active, area.list, frame); category: Category,
Self::render_info_widget("Album info", state.info, state.active, area.info, frame); 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>(&mut self, area: TrackArea, frame: &mut Frame<'_, B>) { fn render_track_column<B: Backend>(
let state = self.construct_track_state(); tracks: &[Track],
Self::render_list_widget("Tracks", state.list, state.active, area.list, frame); category: Category,
Self::render_info_widget("Track info", state.info, state.active, area.info, frame); 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,
);
} }
} }
@ -620,9 +603,13 @@ 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 areas = Self::construct_areas(frame.size());
self.render_artist_column(areas.artists, frame); Self::render_artist_column(
self.render_album_column(areas.albums, frame); self.collection_manager.get_collection(),
self.render_track_column(areas.tracks, frame); self.selection.active,
&mut self.selection.artist,
areas,
frame,
);
} }
} }