Selected item is always at the bottom of list #41
261
src/tui/ui.rs
261
src/tui/ui.rs
@ -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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user