From 66913b8e7678e655b2ed536536d8f061c930b02c Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 7 Mar 2024 22:19:14 +0100 Subject: [PATCH 1/3] Working status --- src/core/collection/album.rs | 65 +++++++++++---------- src/core/collection/track.rs | 10 +++- src/tui/ui.rs | 108 +++++++++++++++++++++++++++++++---- 3 files changed, 140 insertions(+), 43 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 9de9d3b..e56367f 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -8,6 +8,8 @@ use crate::core::collection::{ track::Track, }; +use super::track::TrackFormat; + /// An album is a collection of tracks that were released together. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Album { @@ -40,22 +42,16 @@ pub struct AlbumDate { pub day: u8, } -impl Display for AlbumDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.month.is_none() { - write!(f, "{}", self.year) - } else if self.day == 0 { - write!(f, "{}‐{:02}", self.year, self.month as u8) - } else { - write!(f, "{}‐{:02}‐{:02}", self.year, self.month as u8, self.day) +impl AlbumDate { + pub fn new>(year: u32, month: M, day: u8) -> Self { + AlbumDate { + year, + month: month.into(), + day, } } } -/// The album's sequence to determine order when two or more albums have the same release date. -#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] -pub struct AlbumSeq(pub u8); - #[repr(u8)] #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum AlbumMonth { @@ -96,16 +92,41 @@ impl From for AlbumMonth { } impl AlbumMonth { - fn is_none(&self) -> bool { + pub fn is_none(&self) -> bool { matches!(self, AlbumMonth::None) } } +/// The album's sequence to determine order when two or more albums have the same release date. +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct AlbumSeq(pub u8); + +/// The album's ownership status. +#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub enum AlbumStatus { + #[default] + None, + Owned(TrackFormat), +} + +impl AlbumStatus { + pub fn from_tracks(tracks: &[Track]) -> AlbumStatus { + match tracks.iter().map(|t| t.quality.format).min() { + Some(format) => AlbumStatus::Owned(format), + None => AlbumStatus::None, + } + } +} + impl Album { pub fn get_sort_key(&self) -> (&AlbumDate, &AlbumSeq, &AlbumId) { (&self.date, &self.seq, &self.id) } + pub fn get_status(&self) -> AlbumStatus { + AlbumStatus::from_tracks(&self.tracks) + } + pub fn set_seq(&mut self, seq: AlbumSeq) { self.seq = seq; } @@ -166,16 +187,6 @@ mod tests { use super::*; - impl AlbumDate { - fn new>(year: u32, month: M, day: u8) -> Self { - AlbumDate { - year, - month: month.into(), - day, - } - } - } - #[test] fn album_month() { assert_eq!(>::into(0), AlbumMonth::None); @@ -195,14 +206,6 @@ mod tests { assert_eq!(>::into(255), AlbumMonth::None); } - #[test] - fn album_display() { - assert_eq!(AlbumDate::default().to_string(), "0"); - assert_eq!(AlbumDate::new(1990, 0, 0).to_string(), "1990"); - assert_eq!(AlbumDate::new(1990, 5, 0).to_string(), "1990‐05"); - assert_eq!(AlbumDate::new(1990, 5, 6).to_string(), "1990‐05‐06"); - } - #[test] fn same_date_seq_cmp() { let date = AlbumDate::new(2024, 3, 2); diff --git a/src/core/collection/track.rs b/src/core/collection/track.rs index dc4bc4f..4287669 100644 --- a/src/core/collection/track.rs +++ b/src/core/collection/track.rs @@ -33,10 +33,10 @@ impl Track { } /// The track file format. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum TrackFormat { - Flac, Mp3, + Flac, } impl PartialOrd for Track { @@ -61,6 +61,12 @@ impl Merge for Track { mod tests { use super::*; + #[test] + fn track_ord() { + assert!(TrackFormat::Mp3 < TrackFormat::Flac); + assert!(TrackFormat::Flac > TrackFormat::Mp3); + } + #[test] fn merge_track() { let left = Track { diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 2d71e98..7caafd2 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use musichoard::collection::{ - album::Album, + album::{Album, AlbumDate, AlbumSeq, AlbumStatus}, artist::Artist, - track::{Track, TrackFormat}, + track::{Track, TrackFormat, TrackQuality}, Collection, }; use ratatui::{ @@ -287,13 +287,19 @@ impl<'a, 'b> AlbumState<'a, 'b> { let album = state.list.selected().map(|i| &albums[i]); let info = Paragraph::new(format!( "Title: {}\n\ - Date: {}{}", + Date: {}{}\n\ + Status: {}", album.map(|a| a.id.title.as_str()).unwrap_or(""), - album.map(|a| a.date.to_string()).unwrap_or_default(), + album + .map(|a| Self::display_album_date(&a.date)) + .unwrap_or_default(), album .filter(|a| a.seq.0 > 0) - .map(|a| format!(" ({})", a.seq.0)) - .unwrap_or_default() + .map(|a| Self::display_album_seq(&a.seq)) + .unwrap_or_default(), + album + .map(|a| Self::display_album_status(&a.get_status())) + .unwrap_or("") )); AlbumState { @@ -303,6 +309,30 @@ impl<'a, 'b> AlbumState<'a, 'b> { info, } } + + fn display_album_date(date: &AlbumDate) -> String { + if date.month.is_none() { + format!("{}", date.year) + } else if date.day == 0 { + format!("{}‐{:02}", date.year, date.month as u8) + } else { + format!("{}‐{:02}‐{:02}", date.year, date.month as u8, date.day) + } + } + + fn display_album_seq(seq: &AlbumSeq) -> String { + format!(" ({})", seq.0) + } + + fn display_album_status(status: &AlbumStatus) -> &'static str { + match status { + AlbumStatus::None => "None", + AlbumStatus::Owned(format) => match format { + TrackFormat::Mp3 => "MP3", + TrackFormat::Flac => "FLAC", + }, + } + } } struct TrackState<'a, 'b> { @@ -331,10 +361,7 @@ impl<'a, 'b> TrackState<'a, 'b> { track.map(|t| t.id.title.as_str()).unwrap_or(""), track.map(|t| t.artist.join("; ")).unwrap_or_default(), track - .map(|t| match t.quality.format { - TrackFormat::Flac => "FLAC".to_string(), - TrackFormat::Mp3 => format!("MP3 {}kbps", t.quality.bitrate), - }) + .map(|t| Self::display_track_quality(&t.quality)) .unwrap_or_default(), )); @@ -345,6 +372,13 @@ impl<'a, 'b> TrackState<'a, 'b> { info, } } + + fn display_track_quality(quality: &TrackQuality) -> String { + match quality.format { + TrackFormat::Flac => "FLAC".to_string(), + TrackFormat::Mp3 => format!("MP3 {}kbps", quality.bitrate), + } + } } struct Minibuffer<'a> { @@ -743,6 +777,60 @@ mod tests { terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap(); } + #[test] + fn display_album_date() { + assert_eq!(AlbumState::display_album_date(&AlbumDate::default()), "0"); + assert_eq!( + AlbumState::display_album_date(&AlbumDate::new(1990, 0, 0)), + "1990" + ); + assert_eq!( + AlbumState::display_album_date(&AlbumDate::new(1990, 5, 0)), + "1990‐05" + ); + assert_eq!( + AlbumState::display_album_date(&AlbumDate::new(1990, 5, 6)), + "1990‐05‐06" + ); + } + + #[test] + fn display_album_seq() { + assert_eq!(AlbumState::display_album_seq(&AlbumSeq::default()), " (0)"); + assert_eq!(AlbumState::display_album_seq(&AlbumSeq(5)), " (5)"); + } + + #[test] + fn display_album_status() { + assert_eq!(AlbumState::display_album_status(&AlbumStatus::None), "None"); + assert_eq!( + AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Mp3)), + "MP3" + ); + assert_eq!( + AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Flac)), + "FLAC" + ); + } + + #[test] + fn display_track_quality() { + assert_eq!( + TrackState::display_track_quality(&TrackQuality { + format: TrackFormat::Flac, + bitrate: 1411 + }), + "FLAC" + ); + assert_eq!( + TrackState::display_track_quality(&TrackQuality { + format: TrackFormat::Mp3, + bitrate: 218 + }), + "MP3 218kbps" + ); + } + #[test] fn empty() { let artists: Vec = vec![]; -- 2.45.2 From 61ad0453650f4f0ae44cfec716be02f4fd33b4ca Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 7 Mar 2024 22:58:54 +0100 Subject: [PATCH 2/3] Add colors --- src/core/collection/album.rs | 2 -- src/tui/ui.rs | 48 +++++++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index e56367f..8186f04 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -102,9 +102,7 @@ impl AlbumMonth { pub struct AlbumSeq(pub u8); /// The album's ownership status. -#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] pub enum AlbumStatus { - #[default] None, Owned(TrackFormat), } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 7caafd2..b147c27 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -9,12 +9,20 @@ use musichoard::collection::{ use ratatui::{ layout::{Alignment, Rect}, style::{Color, Style}, + text::Line, widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap}, Frame, }; use crate::tui::app::{AppPublicState, AppState, Category, IAppAccess, Selection, WidgetState}; +const COLOR_BG: Color = Color::Black; +const COLOR_BG_HL: Color = Color::DarkGray; +const COLOR_FG: Color = Color::White; +const COLOR_FG_ERR: Color = Color::Red; +const COLOR_FG_WARN: Color = Color::LightYellow; +const COLOR_FG_GOOD: Color = Color::LightGreen; + pub trait IUi { fn render(app: &mut APP, frame: &mut Frame); } @@ -280,7 +288,7 @@ impl<'a, 'b> AlbumState<'a, 'b> { let list = List::new( albums .iter() - .map(|a| ListItem::new(a.id.title.as_str())) + .map(Self::to_list_item) .collect::>(), ); @@ -310,6 +318,17 @@ impl<'a, 'b> AlbumState<'a, 'b> { } } + fn to_list_item(album: &Album) -> ListItem { + let line = match album.get_status() { + AlbumStatus::None => Line::raw(album.id.title.as_str()), + AlbumStatus::Owned(format) => match format { + TrackFormat::Mp3 => Line::styled(album.id.title.as_str(), COLOR_FG_WARN), + TrackFormat::Flac => Line::styled(album.id.title.as_str(), COLOR_FG_GOOD), + }, + }; + ListItem::new(line) + } + fn display_album_date(date: &AlbumDate) -> String { if date.month.is_none() { format!("{}", date.year) @@ -463,11 +482,11 @@ pub struct Ui; impl Ui { fn style(_active: bool, error: bool) -> Style { - let style = Style::default().bg(Color::Black); + let style = Style::default().bg(COLOR_BG); if error { - style.fg(Color::Red) + style.fg(COLOR_FG_ERR) } else { - style.fg(Color::White) + style.fg(COLOR_FG) } } @@ -476,10 +495,11 @@ impl Ui { } fn highlight_style(active: bool) -> Style { + // Do not set foreground colour to not overwrite any list-specific customisation. if active { - Style::default().fg(Color::White).bg(Color::DarkGray) + Style::default().bg(COLOR_BG_HL) } else { - Self::style(false, false) + Style::default().bg(COLOR_BG) } } @@ -721,6 +741,8 @@ impl IUi for Ui { #[cfg(test)] mod tests { + use musichoard::collection::{album::AlbumId, artist::ArtistId}; + use crate::tui::{ app::{AppPublic, AppPublicInner, Delta}, testmod::COLLECTION, @@ -839,6 +861,20 @@ mod tests { draw_test_suite(&artists, &mut selection); } + #[test] + fn empty_album() { + let mut artists: Vec = vec![Artist::new(ArtistId::new("An artist"))]; + artists[0].albums.push(Album { + id: AlbumId::new("An album"), + date: AlbumDate::default(), + seq: AlbumSeq::default(), + tracks: vec![], + }); + let mut selection = Selection::new(&artists); + + draw_test_suite(&artists, &mut selection); + } + #[test] fn collection() { let artists = &COLLECTION; -- 2.45.2 From a9a90f41627c22e74ce17d07884a0e53c9dd486d Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Thu, 7 Mar 2024 23:09:19 +0100 Subject: [PATCH 3/3] Minor review fixes --- src/core/collection/album.rs | 4 +--- src/tui/ui.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/core/collection/album.rs b/src/core/collection/album.rs index 8186f04..ccbd707 100644 --- a/src/core/collection/album.rs +++ b/src/core/collection/album.rs @@ -5,11 +5,9 @@ use std::{ use crate::core::collection::{ merge::{Merge, MergeSorted, WithId}, - track::Track, + track::{Track, TrackFormat}, }; -use super::track::TrackFormat; - /// An album is a collection of tracks that were released together. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Album { diff --git a/src/tui/ui.rs b/src/tui/ui.rs index b147c27..3959a03 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -302,7 +302,6 @@ impl<'a, 'b> AlbumState<'a, 'b> { .map(|a| Self::display_album_date(&a.date)) .unwrap_or_default(), album - .filter(|a| a.seq.0 > 0) .map(|a| Self::display_album_seq(&a.seq)) .unwrap_or_default(), album @@ -340,7 +339,11 @@ impl<'a, 'b> AlbumState<'a, 'b> { } fn display_album_seq(seq: &AlbumSeq) -> String { - format!(" ({})", seq.0) + if seq.0 > 0 { + format!(" ({})", seq.0) + } else { + String::new() + } } fn display_album_status(status: &AlbumStatus) -> &'static str { @@ -495,7 +498,7 @@ impl Ui { } fn highlight_style(active: bool) -> Style { - // Do not set foreground colour to not overwrite any list-specific customisation. + // Do not set the fg color here as it will overwrite any list-specific customisation. if active { Style::default().bg(COLOR_BG_HL) } else { @@ -818,7 +821,8 @@ mod tests { #[test] fn display_album_seq() { - assert_eq!(AlbumState::display_album_seq(&AlbumSeq::default()), " (0)"); + assert_eq!(AlbumState::display_album_seq(&AlbumSeq::default()), ""); + assert_eq!(AlbumState::display_album_seq(&AlbumSeq(0)), ""); assert_eq!(AlbumState::display_album_seq(&AlbumSeq(5)), " (5)"); } -- 2.45.2