diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 5da457f..657c226 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -67,6 +67,10 @@ impl IEventHandlerPrivate for EventHandler { KeyCode::Down => { ui.increment_selection(); } + // Toggle overlay. + KeyCode::Char('m') | KeyCode::Char('M') => { + ui.toggle_overlay(); + } // Other keys. _ => {} } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 2827147..f996ec9 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1,11 +1,11 @@ use std::fmt; -use musichoard::{Album, Artist, Collection, Format, Track}; +use musichoard::{Album, Artist, Collection, Format, IUrl, Track}; use ratatui::{ backend::Backend, layout::{Alignment, Rect}, style::{Color, Style}, - widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph}, + widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph}, Frame, }; @@ -42,6 +42,7 @@ pub trait IUi { fn increment_selection(&mut self); fn decrement_selection(&mut self); + fn toggle_overlay(&mut self); fn render(&mut self, frame: &mut Frame<'_, B>); } @@ -275,6 +276,7 @@ impl Selection { pub struct Ui { music_hoard: MH, selection: Selection, + overlay: bool, running: bool, } @@ -299,7 +301,7 @@ struct FrameArea { } impl FrameArea { - fn new(frame: Rect) -> FrameArea { + fn new(frame: Rect) -> Self { let width_one_third = frame.width / 3; let height_one_third = frame.height / 3; @@ -357,6 +359,28 @@ impl FrameArea { } } +struct OverlayArea { + artist: Rect, +} + +impl OverlayArea { + fn new(frame: Rect) -> Self { + let margin_factor = 8; + + let width_margin = frame.width / margin_factor; + let height_margin = frame.height / margin_factor; + + let artist = Rect { + x: width_margin, + y: height_margin, + width: frame.width - (2 * width_margin), + height: frame.height - (2 * height_margin), + }; + + OverlayArea { artist } + } +} + struct ArtistState<'a, 'b> { active: bool, list: List<'a>, @@ -380,6 +404,56 @@ impl<'a, 'b> ArtistState<'a, 'b> { } } +struct ArtistOverlay<'a> { + properties: Paragraph<'a>, +} + +impl<'a> ArtistOverlay<'a> { + fn opt_opt_to_str(opt: Option>) -> &str { + opt.flatten().map(|item| item.url()).unwrap_or("") + } + + fn opt_vec_to_string(opt_vec: Option<&Vec>, indent: &str) -> String { + match opt_vec { + Some(vec) => { + if vec.len() < 2 { + vec.get(0).map(|item| item.url()).unwrap_or("").to_string() + } else { + let indent = format!("\n{indent}"); + let list = vec + .iter() + .map(|item| item.url()) + .collect::>() + .join(&indent); + format!("{indent}{list}") + } + } + None => String::from(""), + } + } + + fn new(artists: &'a [Artist], state: &ListState) -> ArtistOverlay<'a> { + let artist = state.selected().map(|i| &artists[i]); + + let item_indent = " "; + let list_indent = " - "; + let properties = Paragraph::new(format!( + "Artist: {}\n\n{item_indent}\ + MusicBrainz: {}\n{item_indent}\ + MusicButler: {}\n{item_indent}\ + Bandcamp: {}\n{item_indent}\ + Qobuz: {}", + artist.map(|a| a.id.name.as_str()).unwrap_or(""), + Self::opt_opt_to_str(artist.map(|a| a.properties.musicbrainz.as_ref())), + Self::opt_vec_to_string(artist.map(|a| &a.properties.musicbutler), list_indent), + Self::opt_vec_to_string(artist.map(|a| &a.properties.bandcamp), list_indent), + Self::opt_opt_to_str(artist.map(|a| a.properties.qobuz.as_ref())), + )); + + ArtistOverlay { properties } + } +} + struct AlbumState<'a, 'b> { active: bool, list: List<'a>, @@ -469,6 +543,7 @@ impl Ui { Ok(Ui { music_hoard, selection, + overlay: false, running: true, }) } @@ -531,6 +606,21 @@ impl Ui { ); } + fn render_overlay_widget( + title: &str, + paragraph: Paragraph, + area: Rect, + frame: &mut Frame<'_, B>, + ) { + frame.render_widget(Clear, area); + frame.render_widget( + paragraph + .style(Self::style(true)) + .block(Self::block(title, true)), + area, + ); + } + fn render_artist_column(st: ArtistState, ar: ArtistArea, fr: &mut Frame<'_, B>) { Self::render_list_widget("Artists", st.list, st.state, st.active, ar.list, fr); } @@ -544,41 +634,8 @@ impl Ui { Self::render_list_widget("Tracks", st.list, st.state, st.active, ar.list, fr); Self::render_info_widget("Track info", st.info, st.active, ar.info, fr); } -} -impl IUi for Ui { - fn is_running(&self) -> bool { - self.running - } - - fn quit(&mut self) { - self.running = false; - } - - fn save(&mut self) -> Result<(), UiError> { - self.music_hoard.save_to_database()?; - Ok(()) - } - - fn increment_category(&mut self) { - self.selection.increment_category(); - } - - fn decrement_category(&mut self) { - self.selection.decrement_category(); - } - - fn increment_selection(&mut self) { - self.selection - .increment_selection(self.music_hoard.get_collection()); - } - - fn decrement_selection(&mut self) { - self.selection - .decrement_selection(self.music_hoard.get_collection()); - } - - fn render(&mut self, frame: &mut Frame<'_, B>) { + fn render_collection(&mut self, frame: &mut Frame<'_, B>) { let active = self.selection.active; let areas = FrameArea::new(frame.size()); @@ -622,6 +679,60 @@ impl IUi for Ui { Self::render_track_column(track_state, areas.track, frame); } + + fn render_overlay(&mut self, frame: &mut Frame<'_, B>) { + let areas = OverlayArea::new(frame.size()); + + let artists = self.music_hoard.get_collection(); + let artist_selection = &mut self.selection.artist; + + let artist_overlay = ArtistOverlay::new(&artists, &artist_selection.state); + Self::render_overlay_widget("Artist", artist_overlay.properties, areas.artist, frame); + } +} + +impl IUi for Ui { + fn is_running(&self) -> bool { + self.running + } + + fn quit(&mut self) { + self.running = false; + } + + fn save(&mut self) -> Result<(), UiError> { + self.music_hoard.save_to_database()?; + Ok(()) + } + + fn increment_category(&mut self) { + self.selection.increment_category(); + } + + fn decrement_category(&mut self) { + self.selection.decrement_category(); + } + + fn increment_selection(&mut self) { + self.selection + .increment_selection(self.music_hoard.get_collection()); + } + + fn decrement_selection(&mut self) { + self.selection + .decrement_selection(self.music_hoard.get_collection()); + } + + fn toggle_overlay(&mut self) { + self.overlay = !self.overlay; + } + + fn render(&mut self, frame: &mut Frame<'_, B>) { + self.render_collection(frame); + if self.overlay { + self.render_overlay(frame); + } + } } #[cfg(test)]