Fix empty list issue
This commit is contained in:
parent
9fd09e9db2
commit
f8b5680dc0
275
src/tui/app.rs
275
src/tui/app.rs
@ -9,11 +9,105 @@ pub enum Category {
|
||||
Track,
|
||||
}
|
||||
|
||||
pub struct Selection {
|
||||
struct TrackSelection {
|
||||
index: u16,
|
||||
}
|
||||
|
||||
impl TrackSelection {
|
||||
fn initialise(tracks: &Vec<Track>) -> Option<TrackSelection> {
|
||||
if !tracks.is_empty() {
|
||||
Some(TrackSelection { index: 0 })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&mut self, tracks: &Vec<Track>) {
|
||||
if let Some(result) = self.index.checked_add(1) {
|
||||
if (result as usize) < tracks.len() {
|
||||
self.index = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement(&mut self, _tracks: &Vec<Track>) {
|
||||
if let Some(result) = self.index.checked_sub(1) {
|
||||
self.index = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AlbumSelection {
|
||||
index: u16,
|
||||
track: Option<TrackSelection>,
|
||||
}
|
||||
|
||||
impl AlbumSelection {
|
||||
fn initialise(albums: &Vec<Album>) -> Option<AlbumSelection> {
|
||||
if !albums.is_empty() {
|
||||
Some(AlbumSelection {
|
||||
index: 0,
|
||||
track: TrackSelection::initialise(&albums[0].tracks),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&mut self, albums: &Vec<Album>) {
|
||||
if let Some(result) = self.index.checked_add(1) {
|
||||
if (result as usize) < albums.len() {
|
||||
self.index = result;
|
||||
self.track = TrackSelection::initialise(&albums[self.index as usize].tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement(&mut self, albums: &Vec<Album>) {
|
||||
if let Some(result) = self.index.checked_sub(1) {
|
||||
self.index = result;
|
||||
self.track = TrackSelection::initialise(&albums[self.index as usize].tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ArtistSelection {
|
||||
index: u16,
|
||||
album: Option<AlbumSelection>,
|
||||
}
|
||||
|
||||
impl ArtistSelection {
|
||||
fn initialise(artists: &Vec<Artist>) -> Option<ArtistSelection> {
|
||||
if !artists.is_empty() {
|
||||
Some(ArtistSelection {
|
||||
index: 0,
|
||||
album: AlbumSelection::initialise(&artists[0].albums),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn increment(&mut self, artists: &Vec<Artist>) {
|
||||
if let Some(result) = self.index.checked_add(1) {
|
||||
if (result as usize) < artists.len() {
|
||||
self.index = result;
|
||||
self.album = AlbumSelection::initialise(&artists[self.index as usize].albums);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement(&mut self, artists: &Vec<Artist>) {
|
||||
if let Some(result) = self.index.checked_sub(1) {
|
||||
self.index = result;
|
||||
self.album = AlbumSelection::initialise(&artists[self.index as usize].albums);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Selection {
|
||||
active: Category,
|
||||
artist: u16,
|
||||
album: u16,
|
||||
track: u16,
|
||||
artist: Option<ArtistSelection>,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
@ -25,14 +119,13 @@ pub struct App {
|
||||
impl App {
|
||||
pub fn new(mut collection_manager: CollectionManager) -> Result<Self, Error> {
|
||||
collection_manager.rescan_library()?;
|
||||
let selection = Selection {
|
||||
active: Category::Artist,
|
||||
artist: ArtistSelection::initialise(collection_manager.get_collection()),
|
||||
};
|
||||
Ok(App {
|
||||
collection_manager,
|
||||
selection: Selection {
|
||||
active: Category::Artist,
|
||||
artist: 0,
|
||||
album: 0,
|
||||
track: 0,
|
||||
},
|
||||
selection,
|
||||
running: true,
|
||||
})
|
||||
}
|
||||
@ -74,56 +167,62 @@ impl App {
|
||||
}
|
||||
|
||||
fn increment_artist_selection(&mut self) {
|
||||
if let Some(result) = self.selection.artist.checked_add(1) {
|
||||
let artists: &Vec<Artist> = self.collection_manager.get_collection();
|
||||
if (result as usize) < artists.len() {
|
||||
self.selection.artist = result;
|
||||
self.selection.album = 0;
|
||||
self.selection.track = 0;
|
||||
}
|
||||
if let Some(ref mut artist_selection) = self.selection.artist {
|
||||
let artists = &self.collection_manager.get_collection();
|
||||
artist_selection.increment(artists);
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement_artist_selection(&mut self) {
|
||||
if let Some(result) = self.selection.artist.checked_sub(1) {
|
||||
self.selection.artist = result;
|
||||
self.selection.album = 0;
|
||||
self.selection.track = 0;
|
||||
if let Some(ref mut artist_selection) = self.selection.artist {
|
||||
let artists = &self.collection_manager.get_collection();
|
||||
artist_selection.decrement(artists);
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_album_selection(&mut self) {
|
||||
if let Some(result) = self.selection.album.checked_add(1) {
|
||||
let artists: &Vec<Artist> = self.collection_manager.get_collection();
|
||||
let albums: &Vec<Album> = &artists[self.selection.artist as usize].albums;
|
||||
if (result as usize) < albums.len() {
|
||||
self.selection.album = result;
|
||||
self.selection.track = 0;
|
||||
if let Some(ref mut artist_selection) = self.selection.artist {
|
||||
if let Some(ref mut album_selection) = artist_selection.album {
|
||||
let artists = &self.collection_manager.get_collection();
|
||||
let albums = &artists[artist_selection.index as usize].albums;
|
||||
album_selection.increment(albums);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement_album_selection(&mut self) {
|
||||
if let Some(result) = self.selection.album.checked_sub(1) {
|
||||
self.selection.album = result;
|
||||
self.selection.track = 0;
|
||||
if let Some(ref mut artist_selection) = self.selection.artist {
|
||||
if let Some(ref mut album_selection) = artist_selection.album {
|
||||
let artists = &self.collection_manager.get_collection();
|
||||
let albums = &artists[artist_selection.index as usize].albums;
|
||||
album_selection.decrement(albums);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_track_selection(&mut self) {
|
||||
if let Some(result) = self.selection.track.checked_add(1) {
|
||||
let artists: &Vec<Artist> = self.collection_manager.get_collection();
|
||||
let albums: &Vec<Album> = &artists[self.selection.artist as usize].albums;
|
||||
let tracks: &Vec<Track> = &albums[self.selection.album as usize].tracks;
|
||||
if (result as usize) < tracks.len() {
|
||||
self.selection.track = result;
|
||||
if let Some(ref mut artist_selection) = self.selection.artist {
|
||||
if let Some(ref mut album_selection) = artist_selection.album {
|
||||
if let Some(ref mut track_selection) = album_selection.track {
|
||||
let artists = &self.collection_manager.get_collection();
|
||||
let albums = &artists[artist_selection.index as usize].albums;
|
||||
let tracks = &albums[album_selection.index as usize].tracks;
|
||||
track_selection.increment(tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decrement_track_selection(&mut self) {
|
||||
if let Some(result) = self.selection.track.checked_sub(1) {
|
||||
self.selection.track = result;
|
||||
if let Some(ref mut artist_selection) = self.selection.artist {
|
||||
if let Some(ref mut album_selection) = artist_selection.album {
|
||||
if let Some(ref mut track_selection) = album_selection.track {
|
||||
let artists = &self.collection_manager.get_collection();
|
||||
let albums = &artists[artist_selection.index as usize].albums;
|
||||
let tracks = &albums[album_selection.index as usize].tracks;
|
||||
track_selection.decrement(tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,40 +230,92 @@ impl App {
|
||||
self.selection.active
|
||||
}
|
||||
|
||||
pub fn get_artists(&self) -> Vec<&ArtistId> {
|
||||
self.collection_manager
|
||||
.get_collection()
|
||||
.iter()
|
||||
.map(|a| &a.id)
|
||||
.collect()
|
||||
fn get_artists(&self) -> Option<&Vec<Artist>> {
|
||||
Some(&self.collection_manager.get_collection())
|
||||
}
|
||||
|
||||
pub fn selected_artist(&self) -> usize {
|
||||
self.selection.artist as usize
|
||||
fn get_albums(&self) -> Option<&Vec<Album>> {
|
||||
if let Some(artists) = self.get_artists() {
|
||||
if let Some(artist_index) = self.selected_artist() {
|
||||
Some(&artists[artist_index].albums)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_albums(&self) -> Vec<&AlbumId> {
|
||||
self.collection_manager.get_collection()[self.selection.artist as usize]
|
||||
.albums
|
||||
.iter()
|
||||
.map(|a| &a.id)
|
||||
.collect()
|
||||
fn get_tracks(&self) -> Option<&Vec<Track>> {
|
||||
if let Some(albums) = self.get_albums() {
|
||||
if let Some(album_index) = self.selected_album() {
|
||||
Some(&albums[album_index].tracks)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_album(&self) -> usize {
|
||||
self.selection.album as usize
|
||||
pub fn get_artist_ids(&self) -> Vec<&ArtistId> {
|
||||
if let Some(artists) = self.get_artists() {
|
||||
artists.iter().map(|a| &a.id).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_tracks(&self) -> Vec<&Track> {
|
||||
self.collection_manager.get_collection()[self.selection.artist as usize].albums
|
||||
[self.selection.album as usize]
|
||||
.tracks
|
||||
.iter()
|
||||
.collect()
|
||||
pub fn get_album_ids(&self) -> Vec<&AlbumId> {
|
||||
if let Some(albums) = self.get_albums() {
|
||||
albums.iter().map(|a| &a.id).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_track(&self) -> usize {
|
||||
self.selection.track as usize
|
||||
pub fn get_track_ids(&self) -> Vec<&Track> {
|
||||
if let Some(tracks) = self.get_tracks() {
|
||||
tracks.iter().collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_artist(&self) -> Option<usize> {
|
||||
if let Some(ref artist_selection) = self.selection.artist {
|
||||
Some(artist_selection.index as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_album(&self) -> Option<usize> {
|
||||
if let Some(ref artist_selection) = self.selection.artist {
|
||||
if let Some(ref album_selection) = artist_selection.album {
|
||||
Some(album_selection.index as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_track(&self) -> Option<usize> {
|
||||
if let Some(ref artist_selection) = self.selection.artist {
|
||||
if let Some(ref album_selection) = artist_selection.album {
|
||||
if let Some(ref track_selection) = album_selection.track {
|
||||
Some(track_selection.index as usize)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
|
111
src/tui/ui.rs
111
src/tui/ui.rs
@ -32,21 +32,23 @@ struct FrameAreas {
|
||||
struct SelectionList<'a> {
|
||||
list: List<'a>,
|
||||
state: ListState,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
struct ArtistState<'a> {
|
||||
list: SelectionList<'a>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
struct AlbumState<'a> {
|
||||
list: SelectionList<'a>,
|
||||
info: Paragraph<'a>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
struct TrackState<'a> {
|
||||
list: SelectionList<'a>,
|
||||
info: Paragraph<'a>,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
struct AppState<'a> {
|
||||
@ -58,10 +60,6 @@ struct AppState<'a> {
|
||||
pub struct Ui {}
|
||||
|
||||
impl Ui {
|
||||
const COLOR_FG: Color = Color::White;
|
||||
const COLOR_BG: Color = Color::Black;
|
||||
const COLOR_HL: Color = Color::DarkGray;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Ui {}
|
||||
}
|
||||
@ -124,7 +122,7 @@ impl Ui {
|
||||
}
|
||||
|
||||
fn construct_artist_list(app: &App) -> ArtistState {
|
||||
let artists = app.get_artists();
|
||||
let artists = app.get_artist_ids();
|
||||
let list = List::new(
|
||||
artists
|
||||
.iter()
|
||||
@ -135,21 +133,18 @@ impl Ui {
|
||||
let selected_artist = app.selected_artist();
|
||||
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(selected_artist));
|
||||
state.select(selected_artist);
|
||||
|
||||
let active = app.get_active_category() == Category::Artist;
|
||||
|
||||
ArtistState {
|
||||
list: SelectionList {
|
||||
list,
|
||||
state,
|
||||
active,
|
||||
},
|
||||
list: SelectionList { list, state },
|
||||
active,
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_album_list(app: &App) -> AlbumState {
|
||||
let albums = app.get_albums();
|
||||
let albums = app.get_album_ids();
|
||||
let list = List::new(
|
||||
albums
|
||||
.iter()
|
||||
@ -160,29 +155,29 @@ impl Ui {
|
||||
let selected_album = app.selected_album();
|
||||
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(selected_album));
|
||||
state.select(selected_album);
|
||||
|
||||
let active = app.get_active_category() == Category::Album;
|
||||
|
||||
let album = albums[selected_album];
|
||||
let album = selected_album.map(|i| albums[i]);
|
||||
let info = Paragraph::new(format!(
|
||||
"Title: {}\n\
|
||||
Year: {}",
|
||||
album.title, album.year,
|
||||
album.map(|a| a.title.as_str()).unwrap_or(""),
|
||||
album
|
||||
.map(|a| a.year.to_string())
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
));
|
||||
|
||||
AlbumState {
|
||||
list: SelectionList {
|
||||
list,
|
||||
state,
|
||||
active,
|
||||
},
|
||||
list: SelectionList { list, state },
|
||||
info,
|
||||
active,
|
||||
}
|
||||
}
|
||||
|
||||
fn construct_track_list(app: &App) -> TrackState {
|
||||
let tracks = app.get_tracks();
|
||||
let tracks = app.get_track_ids();
|
||||
let list = List::new(
|
||||
tracks
|
||||
.iter()
|
||||
@ -193,32 +188,35 @@ impl Ui {
|
||||
let selected_track = app.selected_track();
|
||||
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(selected_track));
|
||||
state.select(selected_track);
|
||||
|
||||
let active = app.get_active_category() == Category::Track;
|
||||
|
||||
let track = tracks[selected_track];
|
||||
let track = selected_track.map(|i| tracks[i]);
|
||||
let info = Paragraph::new(format!(
|
||||
"Track: {}\n\
|
||||
Title: {}\n\
|
||||
Artist: {}\n\
|
||||
Format: {}",
|
||||
track.number,
|
||||
track.title,
|
||||
track.artist.join("; "),
|
||||
match track.format {
|
||||
TrackFormat::Flac => "FLAC",
|
||||
TrackFormat::Mp3 => "MP3",
|
||||
},
|
||||
track
|
||||
.map(|t| t.number.to_string())
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
track.map(|t| t.title.as_str()).unwrap_or(""),
|
||||
track
|
||||
.map(|t| t.artist.join("; "))
|
||||
.unwrap_or_else(|| "".to_string()),
|
||||
track
|
||||
.map(|t| match t.format {
|
||||
TrackFormat::Flac => "FLAC",
|
||||
TrackFormat::Mp3 => "MP3",
|
||||
})
|
||||
.unwrap_or(""),
|
||||
));
|
||||
|
||||
TrackState {
|
||||
list: SelectionList {
|
||||
list,
|
||||
state,
|
||||
active,
|
||||
},
|
||||
list: SelectionList { list, state },
|
||||
info,
|
||||
active,
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,38 +228,44 @@ impl Ui {
|
||||
}
|
||||
}
|
||||
|
||||
fn style() -> Style {
|
||||
Style::default().fg(Self::COLOR_FG).bg(Self::COLOR_BG)
|
||||
fn style(_active: bool) -> Style {
|
||||
Style::default().fg(Color::White).bg(Color::Black)
|
||||
}
|
||||
|
||||
fn block_style(active: bool) -> Style {
|
||||
Self::style(active)
|
||||
}
|
||||
|
||||
fn highlight_style(active: bool) -> Style {
|
||||
Style::default().bg(if active {
|
||||
Self::COLOR_HL
|
||||
if active {
|
||||
Style::default().fg(Color::White).bg(Color::DarkGray)
|
||||
} else {
|
||||
Self::COLOR_BG
|
||||
})
|
||||
Self::style(false)
|
||||
}
|
||||
}
|
||||
|
||||
fn block<'a>(title: &str) -> Block<'a> {
|
||||
fn block<'a>(title: &str, active: bool) -> Block<'a> {
|
||||
Block::default()
|
||||
.title_alignment(Alignment::Center)
|
||||
.borders(Borders::ALL)
|
||||
.border_type(BorderType::Rounded)
|
||||
.style(Self::block_style(active))
|
||||
.title(format!(" {title} "))
|
||||
}
|
||||
|
||||
fn render_list_widget<B: Backend>(
|
||||
title: &str,
|
||||
mut list: SelectionList,
|
||||
active: bool,
|
||||
area: Rect,
|
||||
frame: &mut Frame<'_, B>,
|
||||
) {
|
||||
frame.render_stateful_widget(
|
||||
list.list
|
||||
.highlight_style(Self::highlight_style(list.active))
|
||||
.highlight_style(Self::highlight_style(active))
|
||||
.highlight_symbol(">> ")
|
||||
.style(Self::style())
|
||||
.block(Self::block(title)),
|
||||
.style(Self::style(active))
|
||||
.block(Self::block(title, active)),
|
||||
area,
|
||||
&mut list.state,
|
||||
);
|
||||
@ -270,11 +274,14 @@ impl Ui {
|
||||
fn render_info_widget<B: Backend>(
|
||||
title: &str,
|
||||
paragraph: Paragraph,
|
||||
active: bool,
|
||||
area: Rect,
|
||||
frame: &mut Frame<'_, B>,
|
||||
) {
|
||||
frame.render_widget(
|
||||
paragraph.style(Self::style()).block(Self::block(title)),
|
||||
paragraph
|
||||
.style(Self::style(active))
|
||||
.block(Self::block(title, active)),
|
||||
area,
|
||||
);
|
||||
}
|
||||
@ -284,7 +291,7 @@ impl Ui {
|
||||
area: ArtistArea,
|
||||
frame: &mut Frame<'_, B>,
|
||||
) {
|
||||
Self::render_list_widget("Artists", state.list, area.list, frame);
|
||||
Self::render_list_widget("Artists", state.list, state.active, area.list, frame);
|
||||
}
|
||||
|
||||
fn render_album_column<B: Backend>(
|
||||
@ -292,8 +299,8 @@ impl Ui {
|
||||
area: AlbumArea,
|
||||
frame: &mut Frame<'_, B>,
|
||||
) {
|
||||
Self::render_list_widget("Albums", state.list, area.list, frame);
|
||||
Self::render_info_widget("Album info", state.info, area.info, frame);
|
||||
Self::render_list_widget("Albums", state.list, state.active, area.list, frame);
|
||||
Self::render_info_widget("Album info", state.info, state.active, area.info, frame);
|
||||
}
|
||||
|
||||
fn render_track_column<B: Backend>(
|
||||
@ -301,8 +308,8 @@ impl Ui {
|
||||
area: TrackArea,
|
||||
frame: &mut Frame<'_, B>,
|
||||
) {
|
||||
Self::render_list_widget("Tracks", state.list, area.list, frame);
|
||||
Self::render_info_widget("Track info", state.info, area.info, frame);
|
||||
Self::render_list_widget("Tracks", state.list, state.active, area.list, frame);
|
||||
Self::render_info_widget("Track info", state.info, state.active, area.info, frame);
|
||||
}
|
||||
|
||||
pub fn render<B: Backend>(&mut self, app: &App, frame: &mut Frame<'_, B>) {
|
||||
|
Loading…
Reference in New Issue
Block a user