From f8b5680dc0046d455810cbc87cfa5c927417beae Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Wed, 12 Apr 2023 17:36:20 +0200 Subject: [PATCH] Fix empty list issue --- src/tui/app.rs | 275 ++++++++++++++++++++++++++++++++++++++----------- src/tui/ui.rs | 111 ++++++++++---------- 2 files changed, 272 insertions(+), 114 deletions(-) diff --git a/src/tui/app.rs b/src/tui/app.rs index c7664ba..9e2c31a 100644 --- a/src/tui/app.rs +++ b/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) -> Option { + if !tracks.is_empty() { + Some(TrackSelection { index: 0 }) + } else { + None + } + } + + fn increment(&mut self, tracks: &Vec) { + if let Some(result) = self.index.checked_add(1) { + if (result as usize) < tracks.len() { + self.index = result; + } + } + } + + fn decrement(&mut self, _tracks: &Vec) { + if let Some(result) = self.index.checked_sub(1) { + self.index = result; + } + } +} + +struct AlbumSelection { + index: u16, + track: Option, +} + +impl AlbumSelection { + fn initialise(albums: &Vec) -> Option { + if !albums.is_empty() { + Some(AlbumSelection { + index: 0, + track: TrackSelection::initialise(&albums[0].tracks), + }) + } else { + None + } + } + + fn increment(&mut self, albums: &Vec) { + 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) { + 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, +} + +impl ArtistSelection { + fn initialise(artists: &Vec) -> Option { + if !artists.is_empty() { + Some(ArtistSelection { + index: 0, + album: AlbumSelection::initialise(&artists[0].albums), + }) + } else { + None + } + } + + fn increment(&mut self, artists: &Vec) { + 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) { + 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, } pub struct App { @@ -25,14 +119,13 @@ pub struct App { impl App { pub fn new(mut collection_manager: CollectionManager) -> Result { 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 = 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 = self.collection_manager.get_collection(); - let albums: &Vec = &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 = self.collection_manager.get_collection(); - let albums: &Vec = &artists[self.selection.artist as usize].albums; - let tracks: &Vec = &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> { + Some(&self.collection_manager.get_collection()) } - pub fn selected_artist(&self) -> usize { - self.selection.artist as usize + fn get_albums(&self) -> Option<&Vec> { + 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> { + 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 { + if let Some(ref artist_selection) = self.selection.artist { + Some(artist_selection.index as usize) + } else { + None + } + } + + pub fn selected_album(&self) -> Option { + 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 { + 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) { diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 0600687..86dd56a 100644 --- a/src/tui/ui.rs +++ b/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( 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( 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( @@ -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( @@ -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(&mut self, app: &App, frame: &mut Frame<'_, B>) {