diff --git a/src/core/database/json/testmod.rs b/src/core/database/json/testmod.rs index 11cbd3b..ef5ea55 100644 --- a/src/core/database/json/testmod.rs +++ b/src/core/database/json/testmod.rs @@ -26,6 +26,11 @@ pub static DATABASE_JSON: &str = "[\ \"id\":{\"number\":3,\"title\":\"track a.a.3\"},\ \"artist\":[\"artist a.a.3\"],\ \"quality\":{\"format\":\"Flac\",\"bitrate\":1061}\ + },\ + {\ + \"id\":{\"number\":4,\"title\":\"track a.a.4\"},\ + \"artist\":[\"artist a.a.4\"],\ + \"quality\":{\"format\":\"Flac\",\"bitrate\":1042}\ }\ ]\ },\ @@ -88,6 +93,36 @@ pub static DATABASE_JSON: &str = "[\ \"quality\":{\"format\":\"Mp3\",\"bitrate\":320}\ }\ ]\ + },\ + {\ + \"id\":{\"year\":2009,\"title\":\"album_title b.c\"},\ + \"tracks\":[\ + {\ + \"id\":{\"number\":1,\"title\":\"track b.c.1\"},\ + \"artist\":[\"artist b.c.1\"],\ + \"quality\":{\"format\":\"Mp3\",\"bitrate\":190}\ + },\ + {\ + \"id\":{\"number\":2,\"title\":\"track b.c.2\"},\ + \"artist\":[\"artist b.c.2.1\",\"artist b.c.2.2\"],\ + \"quality\":{\"format\":\"Mp3\",\"bitrate\":120}\ + }\ + ]\ + },\ + {\ + \"id\":{\"year\":2015,\"title\":\"album_title b.d\"},\ + \"tracks\":[\ + {\ + \"id\":{\"number\":1,\"title\":\"track b.d.1\"},\ + \"artist\":[\"artist b.d.1\"],\ + \"quality\":{\"format\":\"Mp3\",\"bitrate\":190}\ + },\ + {\ + \"id\":{\"number\":2,\"title\":\"track b.d.2\"},\ + \"artist\":[\"artist b.d.2.1\",\"artist b.d.2.2\"],\ + \"quality\":{\"format\":\"Mp3\",\"bitrate\":120}\ + }\ + ]\ }\ ]\ },\ @@ -132,5 +167,47 @@ pub static DATABASE_JSON: &str = "[\ ]\ }\ ]\ + },\ + {\ + \"id\":{\"name\":\"album_artist d\"},\ + \"sort\":null,\ + \"properties\":{\ + \"musicbrainz\":null,\ + \"musicbutler\":[],\ + \"bandcamp\":[],\ + \"qobuz\":null\ + },\ + \"albums\":[\ + {\ + \"id\":{\"year\":1995,\"title\":\"album_title d.a\"},\ + \"tracks\":[\ + {\ + \"id\":{\"number\":1,\"title\":\"track d.a.1\"},\ + \"artist\":[\"artist d.a.1\"],\ + \"quality\":{\"format\":\"Mp3\",\"bitrate\":120}\ + },\ + {\ + \"id\":{\"number\":2,\"title\":\"track d.a.2\"},\ + \"artist\":[\"artist d.a.2.1\",\"artist d.a.2.2\"],\ + \"quality\":{\"format\":\"Mp3\",\"bitrate\":120}\ + }\ + ]\ + },\ + {\ + \"id\":{\"year\":2028,\"title\":\"album_title d.b\"},\ + \"tracks\":[\ + {\ + \"id\":{\"number\":1,\"title\":\"track d.b.1\"},\ + \"artist\":[\"artist d.b.1\"],\ + \"quality\":{\"format\":\"Flac\",\"bitrate\":841}\ + },\ + {\ + \"id\":{\"number\":2,\"title\":\"track d.b.2\"},\ + \"artist\":[\"artist d.b.2.1\",\"artist d.b.2.2\"],\ + \"quality\":{\"format\":\"Flac\",\"bitrate\":756}\ + }\ + ]\ + }\ + ]\ }\ ]"; diff --git a/src/core/library/beets/testmod.rs b/src/core/library/beets/testmod.rs index 5d28afb..e52045b 100644 --- a/src/core/library/beets/testmod.rs +++ b/src/core/library/beets/testmod.rs @@ -5,15 +5,24 @@ pub static LIBRARY_BEETS: Lazy> = Lazy::new(|| -> Vec { String::from("album_artist a -*^- -*^- 1998 -*^- album_title a.a -*^- 1 -*^- track a.a.1 -*^- artist a.a.1 -*^- FLAC -*^- 992"), String::from("album_artist a -*^- -*^- 1998 -*^- album_title a.a -*^- 2 -*^- track a.a.2 -*^- artist a.a.2.1; artist a.a.2.2 -*^- MP3 -*^- 320"), String::from("album_artist a -*^- -*^- 1998 -*^- album_title a.a -*^- 3 -*^- track a.a.3 -*^- artist a.a.3 -*^- FLAC -*^- 1061"), + String::from("album_artist a -*^- -*^- 1998 -*^- album_title a.a -*^- 4 -*^- track a.a.4 -*^- artist a.a.4 -*^- FLAC -*^- 1042"), String::from("album_artist a -*^- -*^- 2015 -*^- album_title a.b -*^- 1 -*^- track a.b.1 -*^- artist a.b.1 -*^- FLAC -*^- 1004"), String::from("album_artist a -*^- -*^- 2015 -*^- album_title a.b -*^- 2 -*^- track a.b.2 -*^- artist a.b.2 -*^- FLAC -*^- 1077"), String::from("album_artist b -*^- -*^- 2003 -*^- album_title b.a -*^- 1 -*^- track b.a.1 -*^- artist b.a.1 -*^- MP3 -*^- 190"), String::from("album_artist b -*^- -*^- 2003 -*^- album_title b.a -*^- 2 -*^- track b.a.2 -*^- artist b.a.2.1; artist b.a.2.2 -*^- MP3 -*^- 120"), String::from("album_artist b -*^- -*^- 2008 -*^- album_title b.b -*^- 1 -*^- track b.b.1 -*^- artist b.b.1 -*^- FLAC -*^- 1077"), String::from("album_artist b -*^- -*^- 2008 -*^- album_title b.b -*^- 2 -*^- track b.b.2 -*^- artist b.b.2.1; artist b.b.2.2 -*^- MP3 -*^- 320"), + String::from("album_artist b -*^- -*^- 2009 -*^- album_title b.c -*^- 1 -*^- track b.c.1 -*^- artist b.c.1 -*^- MP3 -*^- 190"), + String::from("album_artist b -*^- -*^- 2009 -*^- album_title b.c -*^- 2 -*^- track b.c.2 -*^- artist b.c.2.1; artist b.c.2.2 -*^- MP3 -*^- 120"), + String::from("album_artist b -*^- -*^- 2015 -*^- album_title b.d -*^- 1 -*^- track b.d.1 -*^- artist b.d.1 -*^- MP3 -*^- 190"), + String::from("album_artist b -*^- -*^- 2015 -*^- album_title b.d -*^- 2 -*^- track b.d.2 -*^- artist b.d.2.1; artist b.d.2.2 -*^- MP3 -*^- 120"), String::from("album_artist c -*^- -*^- 1985 -*^- album_title c.a -*^- 1 -*^- track c.a.1 -*^- artist c.a.1 -*^- MP3 -*^- 320"), String::from("album_artist c -*^- -*^- 1985 -*^- album_title c.a -*^- 2 -*^- track c.a.2 -*^- artist c.a.2.1; artist c.a.2.2 -*^- MP3 -*^- 120"), String::from("album_artist c -*^- -*^- 2018 -*^- album_title c.b -*^- 1 -*^- track c.b.1 -*^- artist c.b.1 -*^- FLAC -*^- 1041"), - String::from("album_artist c -*^- -*^- 2018 -*^- album_title c.b -*^- 2 -*^- track c.b.2 -*^- artist c.b.2.1; artist c.b.2.2 -*^- FLAC -*^- 756") + String::from("album_artist c -*^- -*^- 2018 -*^- album_title c.b -*^- 2 -*^- track c.b.2 -*^- artist c.b.2.1; artist c.b.2.2 -*^- FLAC -*^- 756"), + String::from("album_artist d -*^- -*^- 1995 -*^- album_title d.a -*^- 1 -*^- track d.a.1 -*^- artist d.a.1 -*^- MP3 -*^- 120"), + String::from("album_artist d -*^- -*^- 1995 -*^- album_title d.a -*^- 2 -*^- track d.a.2 -*^- artist d.a.2.1; artist d.a.2.2 -*^- MP3 -*^- 120"), + String::from("album_artist d -*^- -*^- 2028 -*^- album_title d.b -*^- 1 -*^- track d.b.1 -*^- artist d.b.1 -*^- FLAC -*^- 841"), + String::from("album_artist d -*^- -*^- 2028 -*^- album_title d.b -*^- 2 -*^- track d.b.2 -*^- artist d.b.2.1; artist d.b.2.2 -*^- FLAC -*^- 756") ] }); diff --git a/src/core/library/testmod.rs b/src/core/library/testmod.rs index a0a5af4..f86be94 100644 --- a/src/core/library/testmod.rs +++ b/src/core/library/testmod.rs @@ -40,6 +40,17 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { track_format: Format::Flac, track_bitrate: 1061, }, + Item { + album_artist: String::from("album_artist a"), + album_artist_sort: None, + album_year: 1998, + album_title: String::from("album_title a.a"), + track_number: 4, + track_title: String::from("track a.a.4"), + track_artist: vec![String::from("artist a.a.4")], + track_format: Format::Flac, + track_bitrate: 1042, + }, Item { album_artist: String::from("album_artist a"), album_artist_sort: None, @@ -112,6 +123,56 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { track_format: Format::Mp3, track_bitrate: 320, }, + Item { + album_artist: String::from("album_artist b"), + album_artist_sort: None, + album_year: 2009, + album_title: String::from("album_title b.c"), + track_number: 1, + track_title: String::from("track b.c.1"), + track_artist: vec![String::from("artist b.c.1")], + track_format: Format::Mp3, + track_bitrate: 190, + }, + Item { + album_artist: String::from("album_artist b"), + album_artist_sort: None, + album_year: 2009, + album_title: String::from("album_title b.c"), + track_number: 2, + track_title: String::from("track b.c.2"), + track_artist: vec![ + String::from("artist b.c.2.1"), + String::from("artist b.c.2.2"), + ], + track_format: Format::Mp3, + track_bitrate: 120, + }, + Item { + album_artist: String::from("album_artist b"), + album_artist_sort: None, + album_year: 2015, + album_title: String::from("album_title b.d"), + track_number: 1, + track_title: String::from("track b.d.1"), + track_artist: vec![String::from("artist b.d.1")], + track_format: Format::Mp3, + track_bitrate: 190, + }, + Item { + album_artist: String::from("album_artist b"), + album_artist_sort: None, + album_year: 2015, + album_title: String::from("album_title b.d"), + track_number: 2, + track_title: String::from("track b.d.2"), + track_artist: vec![ + String::from("artist b.d.2.1"), + String::from("artist b.d.2.2"), + ], + track_format: Format::Mp3, + track_bitrate: 120, + }, Item { album_artist: String::from("album_artist c"), album_artist_sort: None, @@ -162,5 +223,55 @@ pub static LIBRARY_ITEMS: Lazy> = Lazy::new(|| -> Vec { track_format: Format::Flac, track_bitrate: 756, }, + Item { + album_artist: String::from("album_artist d"), + album_artist_sort: None, + album_year: 1995, + album_title: String::from("album_title d.a"), + track_number: 1, + track_title: String::from("track d.a.1"), + track_artist: vec![String::from("artist d.a.1")], + track_format: Format::Mp3, + track_bitrate: 120, + }, + Item { + album_artist: String::from("album_artist d"), + album_artist_sort: None, + album_year: 1995, + album_title: String::from("album_title d.a"), + track_number: 2, + track_title: String::from("track d.a.2"), + track_artist: vec![ + String::from("artist d.a.2.1"), + String::from("artist d.a.2.2"), + ], + track_format: Format::Mp3, + track_bitrate: 120, + }, + Item { + album_artist: String::from("album_artist d"), + album_artist_sort: None, + album_year: 2028, + album_title: String::from("album_title d.b"), + track_number: 1, + track_title: String::from("track d.b.1"), + track_artist: vec![String::from("artist d.b.1")], + track_format: Format::Flac, + track_bitrate: 841, + }, + Item { + album_artist: String::from("album_artist d"), + album_artist_sort: None, + album_year: 2028, + album_title: String::from("album_title d.b"), + track_number: 2, + track_title: String::from("track d.b.2"), + track_artist: vec![ + String::from("artist d.b.2.1"), + String::from("artist d.b.2.2"), + ], + track_format: Format::Flac, + track_bitrate: 756, + }, ] }); diff --git a/src/tests.rs b/src/tests.rs index 84b1372..e952266 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -55,6 +55,17 @@ macro_rules! library_collection { bitrate: 1061, }, }, + Track { + id: TrackId { + number: 4, + title: "track a.a.4".to_string(), + }, + artist: vec!["artist a.a.4".to_string()], + quality: Quality { + format: Format::Flac, + bitrate: 1042, + }, + }, ], }, Album { @@ -167,6 +178,72 @@ macro_rules! library_collection { }, ], }, + Album { + id: AlbumId { + year: 2009, + title: "album_title b.c".to_string(), + }, + tracks: vec![ + Track { + id: TrackId { + number: 1, + title: "track b.c.1".to_string(), + }, + artist: vec!["artist b.c.1".to_string()], + quality: Quality { + format: Format::Mp3, + bitrate: 190, + }, + }, + Track { + id: TrackId { + number: 2, + title: "track b.c.2".to_string(), + }, + artist: vec![ + "artist b.c.2.1".to_string(), + "artist b.c.2.2".to_string(), + ], + quality: Quality { + format: Format::Mp3, + bitrate: 120, + }, + }, + ], + }, + Album { + id: AlbumId { + year: 2015, + title: "album_title b.d".to_string(), + }, + tracks: vec![ + Track { + id: TrackId { + number: 1, + title: "track b.d.1".to_string(), + }, + artist: vec!["artist b.d.1".to_string()], + quality: Quality { + format: Format::Mp3, + bitrate: 190, + }, + }, + Track { + id: TrackId { + number: 2, + title: "track b.d.2".to_string(), + }, + artist: vec![ + "artist b.d.2.1".to_string(), + "artist b.d.2.2".to_string(), + ], + quality: Quality { + format: Format::Mp3, + bitrate: 120, + }, + }, + ], + }, ], }, Artist { @@ -249,6 +326,86 @@ macro_rules! library_collection { }, ], }, + Artist { + id: ArtistId { + name: "album_artist d".to_string(), + }, + sort: None, + properties: ArtistProperties { + musicbrainz: None, + musicbutler: vec![], + bandcamp: vec![], + qobuz: None, + }, + albums: vec![ + Album { + id: AlbumId { + year: 1995, + title: "album_title d.a".to_string(), + }, + tracks: vec![ + Track { + id: TrackId { + number: 1, + title: "track d.a.1".to_string(), + }, + artist: vec!["artist d.a.1".to_string()], + quality: Quality { + format: Format::Mp3, + bitrate: 120, + }, + }, + Track { + id: TrackId { + number: 2, + title: "track d.a.2".to_string(), + }, + artist: vec![ + "artist d.a.2.1".to_string(), + "artist d.a.2.2".to_string(), + ], + quality: Quality { + format: Format::Mp3, + bitrate: 120, + }, + }, + ], + }, + Album { + id: AlbumId { + year: 2028, + title: "album_title d.b".to_string(), + }, + tracks: vec![ + Track { + id: TrackId { + number: 1, + title: "track d.b.1".to_string(), + }, + artist: vec!["artist d.b.1".to_string()], + quality: Quality { + format: Format::Flac, + bitrate: 841, + }, + }, + Track { + id: TrackId { + number: 2, + title: "track d.b.2".to_string(), + }, + artist: vec![ + "artist d.b.2.1".to_string(), + "artist d.b.2.2".to_string(), + ], + quality: Quality { + format: Format::Flac, + bitrate: 756, + }, + }, + ], + }, + ], + }, ] }; } @@ -318,6 +475,8 @@ macro_rules! full_collection { qobuz: None, }; + // Nothing for artist_d + collection }}; } diff --git a/src/tui/app/app.rs b/src/tui/app/app.rs index 0b23320..9ebf397 100644 --- a/src/tui/app/app.rs +++ b/src/tui/app/app.rs @@ -3,7 +3,7 @@ use musichoard::collection::Collection; use crate::tui::{ - app::selection::{ActiveSelection, Selection}, + app::selection::{ActiveSelection, Delta, Selection}, lib::IMusicHoard, }; @@ -50,8 +50,8 @@ pub trait IAppInteract { pub trait IAppInteractBrowse { fn increment_category(&mut self); fn decrement_category(&mut self); - fn increment_selection(&mut self); - fn decrement_selection(&mut self); + fn increment_selection(&mut self, delta: Delta); + fn decrement_selection(&mut self, delta: Delta); fn show_info_overlay(&mut self); @@ -160,14 +160,14 @@ impl IAppInteractBrowse for App { self.selection.decrement_category(); } - fn increment_selection(&mut self) { + fn increment_selection(&mut self, delta: Delta) { self.selection - .increment_selection(self.music_hoard.get_collection()); + .increment_selection(self.music_hoard.get_collection(), delta); } - fn decrement_selection(&mut self) { + fn decrement_selection(&mut self, delta: Delta) { self.selection - .decrement_selection(self.music_hoard.get_collection()); + .decrement_selection(self.music_hoard.get_collection(), delta); } fn show_info_overlay(&mut self) { @@ -362,87 +362,126 @@ mod tests { assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); app.increment_category(); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); app.increment_category(); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(1) + ); app.increment_category(); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(1) + ); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); - app.increment_selection(); + app.increment_selection(Delta::Line); app.decrement_category(); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(1)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(1) + ); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); - app.increment_selection(); + app.increment_selection(Delta::Line); app.decrement_category(); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(1)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); app.increment_category(); - app.increment_selection(); + app.increment_selection(Delta::Line); app.decrement_category(); - app.decrement_selection(); + app.decrement_selection(Delta::Line); app.decrement_category(); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(1)); - assert_eq!(app.selection.artist.album.track.state.selected(), Some(0)); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(1)); + assert_eq!( + app.selection.artist.album.track.state.list.selected(), + Some(0) + ); } #[test] @@ -454,24 +493,24 @@ mod tests { assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); app.increment_category(); app.increment_category(); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); } #[test] @@ -483,37 +522,37 @@ mod tests { assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); app.increment_category(); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); app.increment_category(); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), Some(0)); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), Some(0)); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); } #[test] @@ -522,49 +561,49 @@ mod tests { assert!(app.is_running()); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Artist); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); app.increment_category(); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Album); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); app.increment_category(); - app.increment_selection(); + app.increment_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); - app.decrement_selection(); + app.decrement_selection(Delta::Line); assert_eq!(app.selection.active, Category::Track); - assert_eq!(app.selection.artist.state.selected(), None); - assert_eq!(app.selection.artist.album.state.selected(), None); - assert_eq!(app.selection.artist.album.track.state.selected(), None); + assert_eq!(app.selection.artist.state.list.selected(), None); + assert_eq!(app.selection.artist.album.state.list.selected(), None); + assert_eq!(app.selection.artist.album.track.state.list.selected(), None); } #[test] diff --git a/src/tui/app/selection.rs b/src/tui/app/selection.rs index c51c22b..6438bd0 100644 --- a/src/tui/app/selection.rs +++ b/src/tui/app/selection.rs @@ -13,43 +13,51 @@ pub enum Category { Track, } +#[derive(Clone, Debug, Default)] +pub struct WidgetState { + pub list: ListState, + pub height: usize, +} + +impl PartialEq for WidgetState { + fn eq(&self, other: &Self) -> bool { + self.list.selected().eq(&other.list.selected()) && self.height.eq(&other.height) + } +} + pub struct Selection { pub active: Category, pub artist: ArtistSelection, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct ArtistSelection { - pub state: ListState, + pub state: WidgetState, pub album: AlbumSelection, } -impl PartialEq for ArtistSelection { - fn eq(&self, other: &Self) -> bool { - self.state.selected().eq(&other.state.selected()) && self.album.eq(&other.album) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct AlbumSelection { - pub state: ListState, + pub state: WidgetState, pub track: TrackSelection, } -impl PartialEq for AlbumSelection { - fn eq(&self, other: &Self) -> bool { - self.state.selected().eq(&other.state.selected()) && self.track.eq(&other.track) - } -} - -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct TrackSelection { - pub state: ListState, + pub state: WidgetState, } -impl PartialEq for TrackSelection { - fn eq(&self, other: &Self) -> bool { - self.state.selected().eq(&other.state.selected()) +pub enum Delta { + Line, + Page, +} + +impl Delta { + fn as_usize(&self, state: &WidgetState) -> usize { + match self { + Delta::Line => 1, + Delta::Page => state.height.saturating_sub(1), + } } } @@ -81,51 +89,51 @@ impl Selection { }; } - pub fn increment_selection(&mut self, collection: &Collection) { + pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) { match self.active { - Category::Artist => self.increment_artist(collection), - Category::Album => self.increment_album(collection), - Category::Track => self.increment_track(collection), + Category::Artist => self.increment_artist(collection, delta), + Category::Album => self.increment_album(collection, delta), + Category::Track => self.increment_track(collection, delta), } } - pub fn decrement_selection(&mut self, collection: &Collection) { + pub fn decrement_selection(&mut self, collection: &Collection, delta: Delta) { match self.active { - Category::Artist => self.decrement_artist(collection), - Category::Album => self.decrement_album(collection), - Category::Track => self.decrement_track(collection), + Category::Artist => self.decrement_artist(collection, delta), + Category::Album => self.decrement_album(collection, delta), + Category::Track => self.decrement_track(collection, delta), } } - fn increment_artist(&mut self, artists: &[Artist]) { - self.artist.increment(artists); + fn increment_artist(&mut self, artists: &[Artist], delta: Delta) { + self.artist.increment(artists, delta); } - fn decrement_artist(&mut self, artists: &[Artist]) { - self.artist.decrement(artists); + fn decrement_artist(&mut self, artists: &[Artist], delta: Delta) { + self.artist.decrement(artists, delta); } - fn increment_album(&mut self, artists: &[Artist]) { - self.artist.increment_album(artists); + fn increment_album(&mut self, artists: &[Artist], delta: Delta) { + self.artist.increment_album(artists, delta); } - fn decrement_album(&mut self, artists: &[Artist]) { - self.artist.decrement_album(artists); + fn decrement_album(&mut self, artists: &[Artist], delta: Delta) { + self.artist.decrement_album(artists, delta); } - fn increment_track(&mut self, artists: &[Artist]) { - self.artist.increment_track(artists); + fn increment_track(&mut self, artists: &[Artist], delta: Delta) { + self.artist.increment_track(artists, delta); } - fn decrement_track(&mut self, artists: &[Artist]) { - self.artist.decrement_track(artists); + fn decrement_track(&mut self, artists: &[Artist], delta: Delta) { + self.artist.decrement_track(artists, delta); } } impl ArtistSelection { fn initialise(artists: &[Artist]) -> Self { let mut selection = ArtistSelection { - state: ListState::default(), + state: WidgetState::default(), album: AlbumSelection::initialise(&[]), }; selection.reinitialise(artists, None); @@ -151,60 +159,71 @@ impl ArtistSelection { active_album: Option, ) { if artists.is_empty() { - self.state.select(None); + self.state.list.select(None); self.album = AlbumSelection::initialise(&[]); } else if index >= artists.len() { let end = artists.len() - 1; - self.state.select(Some(end)); + self.state.list.select(Some(end)); self.album = AlbumSelection::initialise(&artists[end].albums); } else { - self.state.select(Some(index)); + self.state.list.select(Some(index)); self.album .reinitialise(&artists[index].albums, active_album); } } - fn increment(&mut self, artists: &[Artist]) { - if let Some(index) = self.state.selected() { - if let Some(result) = index.checked_add(1) { - if result < artists.len() { - self.state.select(Some(result)); - self.album = AlbumSelection::initialise(&artists[result].albums); - } + fn increment_by(&mut self, artists: &[Artist], by: usize) { + if let Some(index) = self.state.list.selected() { + let mut result = index.saturating_add(by); + if result >= artists.len() { + result = artists.len() - 1; } - } - } - - fn increment_album(&mut self, artists: &[Artist]) { - if let Some(index) = self.state.selected() { - self.album.increment(&artists[index].albums); - } - } - - fn increment_track(&mut self, artists: &[Artist]) { - if let Some(index) = self.state.selected() { - self.album.increment_track(&artists[index].albums); - } - } - - fn decrement(&mut self, artists: &[Artist]) { - if let Some(index) = self.state.selected() { - if let Some(result) = index.checked_sub(1) { - self.state.select(Some(result)); + if self.state.list.selected() != Some(result) { + self.state.list.select(Some(result)); self.album = AlbumSelection::initialise(&artists[result].albums); } } } - fn decrement_album(&mut self, artists: &[Artist]) { - if let Some(index) = self.state.selected() { - self.album.decrement(&artists[index].albums); + fn increment(&mut self, artists: &[Artist], delta: Delta) { + self.increment_by(artists, delta.as_usize(&self.state)); + } + + fn increment_album(&mut self, artists: &[Artist], delta: Delta) { + if let Some(index) = self.state.list.selected() { + self.album.increment(&artists[index].albums, delta); } } - fn decrement_track(&mut self, artists: &[Artist]) { - if let Some(index) = self.state.selected() { - self.album.decrement_track(&artists[index].albums); + fn increment_track(&mut self, artists: &[Artist], delta: Delta) { + if let Some(index) = self.state.list.selected() { + self.album.increment_track(&artists[index].albums, delta); + } + } + + fn decrement_by(&mut self, artists: &[Artist], by: usize) { + if let Some(index) = self.state.list.selected() { + let result = index.saturating_sub(by); + if self.state.list.selected() != Some(result) { + self.state.list.select(Some(result)); + self.album = AlbumSelection::initialise(&artists[result].albums); + } + } + } + + fn decrement(&mut self, artists: &[Artist], delta: Delta) { + self.decrement_by(artists, delta.as_usize(&self.state)); + } + + fn decrement_album(&mut self, artists: &[Artist], delta: Delta) { + if let Some(index) = self.state.list.selected() { + self.album.decrement(&artists[index].albums, delta); + } + } + + fn decrement_track(&mut self, artists: &[Artist], delta: Delta) { + if let Some(index) = self.state.list.selected() { + self.album.decrement_track(&artists[index].albums, delta); } } } @@ -212,7 +231,7 @@ impl ArtistSelection { impl AlbumSelection { fn initialise(albums: &[Album]) -> Self { let mut selection = AlbumSelection { - state: ListState::default(), + state: WidgetState::default(), track: TrackSelection::initialise(&[]), }; selection.reinitialise(albums, None); @@ -238,47 +257,58 @@ impl AlbumSelection { active_track: Option, ) { if albums.is_empty() { - self.state.select(None); + self.state.list.select(None); self.track = TrackSelection::initialise(&[]); } else if index >= albums.len() { let end = albums.len() - 1; - self.state.select(Some(end)); + self.state.list.select(Some(end)); self.track = TrackSelection::initialise(&albums[end].tracks); } else { - self.state.select(Some(index)); + self.state.list.select(Some(index)); self.track.reinitialise(&albums[index].tracks, active_track); } } - fn increment(&mut self, albums: &[Album]) { - if let Some(index) = self.state.selected() { - if let Some(result) = index.checked_add(1) { - if result < albums.len() { - self.state.select(Some(result)); - self.track = TrackSelection::initialise(&albums[result].tracks); - } + fn increment_by(&mut self, albums: &[Album], by: usize) { + if let Some(index) = self.state.list.selected() { + let mut result = index.saturating_add(by); + if result >= albums.len() { + result = albums.len() - 1; } - } - } - - fn increment_track(&mut self, albums: &[Album]) { - if let Some(index) = self.state.selected() { - self.track.increment(&albums[index].tracks); - } - } - - fn decrement(&mut self, albums: &[Album]) { - if let Some(index) = self.state.selected() { - if let Some(result) = index.checked_sub(1) { - self.state.select(Some(result)); + if self.state.list.selected() != Some(result) { + self.state.list.select(Some(result)); self.track = TrackSelection::initialise(&albums[result].tracks); } } } - fn decrement_track(&mut self, albums: &[Album]) { - if let Some(index) = self.state.selected() { - self.track.decrement(&albums[index].tracks); + fn increment(&mut self, albums: &[Album], delta: Delta) { + self.increment_by(albums, delta.as_usize(&self.state)); + } + + fn increment_track(&mut self, albums: &[Album], delta: Delta) { + if let Some(index) = self.state.list.selected() { + self.track.increment(&albums[index].tracks, delta); + } + } + + fn decrement_by(&mut self, albums: &[Album], by: usize) { + if let Some(index) = self.state.list.selected() { + let result = index.saturating_sub(by); + if self.state.list.selected() != Some(result) { + self.state.list.select(Some(result)); + self.track = TrackSelection::initialise(&albums[result].tracks); + } + } + } + + fn decrement(&mut self, albums: &[Album], delta: Delta) { + self.decrement_by(albums, delta.as_usize(&self.state)); + } + + fn decrement_track(&mut self, albums: &[Album], delta: Delta) { + if let Some(index) = self.state.list.selected() { + self.track.decrement(&albums[index].tracks, delta); } } } @@ -286,7 +316,7 @@ impl AlbumSelection { impl TrackSelection { fn initialise(tracks: &[Track]) -> Self { let mut selection = TrackSelection { - state: ListState::default(), + state: WidgetState::default(), }; selection.reinitialise(tracks, None); selection @@ -305,31 +335,42 @@ impl TrackSelection { fn reinitialise_with_index(&mut self, tracks: &[Track], index: usize) { if tracks.is_empty() { - self.state.select(None); + self.state.list.select(None); } else if index >= tracks.len() { - self.state.select(Some(tracks.len() - 1)); + self.state.list.select(Some(tracks.len() - 1)); } else { - self.state.select(Some(index)); + self.state.list.select(Some(index)); } } - fn increment(&mut self, tracks: &[Track]) { - if let Some(index) = self.state.selected() { - if let Some(result) = index.checked_add(1) { - if result < tracks.len() { - self.state.select(Some(result)); - } + fn increment_by(&mut self, tracks: &[Track], by: usize) { + if let Some(index) = self.state.list.selected() { + let mut result = index.saturating_add(by); + if result >= tracks.len() { + result = tracks.len() - 1; + } + if self.state.list.selected() != Some(result) { + self.state.list.select(Some(result)); } } } - fn decrement(&mut self, _tracks: &[Track]) { - if let Some(index) = self.state.selected() { - if let Some(result) = index.checked_sub(1) { - self.state.select(Some(result)); + fn increment(&mut self, tracks: &[Track], delta: Delta) { + self.increment_by(tracks, delta.as_usize(&self.state)); + } + + fn decrement_by(&mut self, _tracks: &[Track], by: usize) { + if let Some(index) = self.state.list.selected() { + let result = index.saturating_sub(by); + if self.state.list.selected() != Some(result) { + self.state.list.select(Some(result)); } } } + + fn decrement(&mut self, tracks: &[Track], delta: Delta) { + self.decrement_by(tracks, delta.as_usize(&self.state)); + } } pub struct ActiveSelection { @@ -360,7 +401,7 @@ impl ActiveSelection { impl ActiveArtist { fn get(artists: &[Artist], selection: &ArtistSelection) -> Option { - selection.state.selected().map(|index| { + selection.state.list.selected().map(|index| { let artist = &artists[index]; ActiveArtist { artist_id: artist.get_sort_key().clone(), @@ -372,7 +413,7 @@ impl ActiveArtist { impl ActiveAlbum { fn get(albums: &[Album], selection: &AlbumSelection) -> Option { - selection.state.selected().map(|index| { + selection.state.list.selected().map(|index| { let album = &albums[index]; ActiveAlbum { album_id: album.get_sort_key().clone(), @@ -384,7 +425,7 @@ impl ActiveAlbum { impl ActiveTrack { fn get(tracks: &[Track], selection: &TrackSelection) -> Option { - selection.state.selected().map(|index| { + selection.state.list.selected().map(|index| { let track = &tracks[index]; ActiveTrack { track_id: track.get_sort_key().clone(), @@ -405,24 +446,62 @@ mod tests { assert!(tracks.len() > 1); let empty = TrackSelection::initialise(&[]); - assert_eq!(empty.state.selected(), None); + assert_eq!(empty.state.list.selected(), None); let mut sel = TrackSelection::initialise(tracks); - assert_eq!(sel.state.selected(), Some(0)); + assert_eq!(sel.state.list.selected(), Some(0)); - sel.decrement(tracks); - assert_eq!(sel.state.selected(), Some(0)); + sel.decrement(tracks, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); - sel.increment(tracks); - assert_eq!(sel.state.selected(), Some(1)); + sel.increment(tracks, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(1)); - sel.decrement(tracks); - assert_eq!(sel.state.selected(), Some(0)); + sel.decrement(tracks, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); for _ in 0..(tracks.len() + 5) { - sel.increment(tracks); + sel.increment(tracks, Delta::Line); } - assert_eq!(sel.state.selected(), Some(tracks.len() - 1)); + assert_eq!(sel.state.list.selected(), Some(tracks.len() - 1)); + } + + #[test] + fn track_delta_page() { + let tracks = &COLLECTION[0].albums[0].tracks; + assert!(tracks.len() > 1); + + let empty = TrackSelection::initialise(&[]); + assert_eq!(empty.state.list.selected(), None); + + let mut sel = TrackSelection::initialise(tracks); + assert_eq!(sel.state.list.selected(), Some(0)); + + assert!(tracks.len() >= 4); + sel.state.height = 3; + + sel.decrement(tracks, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(0)); + + sel.increment(tracks, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(2)); + + sel.decrement(tracks, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(0)); + + for _ in 0..(tracks.len() + 5) { + sel.increment(tracks, Delta::Page); + } + assert_eq!(sel.state.list.selected(), Some(tracks.len() - 1)); + } + + #[test] + fn track_reinitialise() { + let tracks = &COLLECTION[0].albums[0].tracks; + assert!(tracks.len() > 1); + + let mut sel = TrackSelection::initialise(tracks); + sel.state.list.select(Some(tracks.len() - 1)); // Re-initialise. let expected = sel.clone(); @@ -432,7 +511,7 @@ mod tests { // Re-initialise out-of-bounds. let mut expected = sel.clone(); - expected.decrement(tracks); + expected.decrement(tracks, Delta::Line); let active_track = ActiveTrack::get(tracks, &sel); sel.reinitialise(&tracks[..(tracks.len() - 1)], active_track); assert_eq!(sel, expected); @@ -442,13 +521,6 @@ mod tests { let active_track = ActiveTrack::get(tracks, &sel); sel.reinitialise(&[], active_track); assert_eq!(sel, expected); - - // Artifical test case to verify upper limit. - sel.state.select(Some(std::usize::MAX)); - assert_eq!(sel.state.selected(), Some(std::usize::MAX)); - - sel.increment(&[]); - assert_eq!(sel.state.selected(), Some(std::usize::MAX)); } #[test] @@ -457,43 +529,101 @@ mod tests { assert!(albums.len() > 1); let empty = AlbumSelection::initialise(&[]); - assert_eq!(empty.state.selected(), None); + assert_eq!(empty.state.list.selected(), None); let mut sel = AlbumSelection::initialise(albums); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.track.state.selected(), Some(0)); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(0)); - sel.increment_track(albums); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.track.state.selected(), Some(1)); + sel.increment_track(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(1)); // Verify that decrement that doesn't change index does not reset track. - sel.decrement(albums); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.track.state.selected(), Some(1)); + sel.decrement(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(1)); - sel.increment(albums); - assert_eq!(sel.state.selected(), Some(1)); - assert_eq!(sel.track.state.selected(), Some(0)); + sel.increment(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(1)); + assert_eq!(sel.track.state.list.selected(), Some(0)); - sel.decrement(albums); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.track.state.selected(), Some(0)); + sel.decrement(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(0)); for _ in 0..(albums.len() + 5) { - sel.increment(albums); + sel.increment(albums, Delta::Line); } - assert_eq!(sel.state.selected(), Some(albums.len() - 1)); - assert_eq!(sel.track.state.selected(), Some(0)); + assert_eq!(sel.state.list.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.list.selected(), Some(0)); - sel.increment_track(albums); - assert_eq!(sel.state.selected(), Some(albums.len() - 1)); - assert_eq!(sel.track.state.selected(), Some(1)); + sel.increment_track(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.list.selected(), Some(1)); // Verify that increment that doesn't change index does not reset track. - sel.increment(albums); - assert_eq!(sel.state.selected(), Some(albums.len() - 1)); - assert_eq!(sel.track.state.selected(), Some(1)); + sel.increment(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.list.selected(), Some(1)); + } + + #[test] + fn album_delta_page() { + let albums = &COLLECTION[1].albums; + assert!(albums.len() > 1); + + let empty = AlbumSelection::initialise(&[]); + assert_eq!(empty.state.list.selected(), None); + + let mut sel = AlbumSelection::initialise(albums); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(0)); + + assert!(albums.len() >= 4); + sel.state.height = 3; + + sel.increment_track(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(1)); + + // Verify that decrement that doesn't change index does not reset track. + sel.decrement(albums, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(1)); + + sel.increment(albums, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(2)); + assert_eq!(sel.track.state.list.selected(), Some(0)); + + sel.decrement(albums, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.track.state.list.selected(), Some(0)); + + for _ in 0..(albums.len() + 5) { + sel.increment(albums, Delta::Page); + } + assert_eq!(sel.state.list.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.list.selected(), Some(0)); + + sel.increment_track(albums, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.list.selected(), Some(1)); + + // Verify that increment that doesn't change index does not reset track. + sel.increment(albums, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(albums.len() - 1)); + assert_eq!(sel.track.state.list.selected(), Some(1)); + } + + #[test] + fn album_reinitialise() { + let albums = &COLLECTION[0].albums; + assert!(albums.len() > 1); + + let mut sel = AlbumSelection::initialise(albums); + sel.state.list.select(Some(albums.len() - 1)); + sel.track.state.list.select(Some(1)); // Re-initialise. let expected = sel.clone(); @@ -503,7 +633,7 @@ mod tests { // Re-initialise out-of-bounds. let mut expected = sel.clone(); - expected.decrement(albums); + expected.decrement(albums, Delta::Line); let active_album = ActiveAlbum::get(albums, &sel); sel.reinitialise(&albums[..(albums.len() - 1)], active_album); assert_eq!(sel, expected); @@ -513,16 +643,6 @@ mod tests { let active_album = ActiveAlbum::get(albums, &sel); sel.reinitialise(&[], active_album); assert_eq!(sel, expected); - - // Artifical test case to verify upper limit. - sel.state.select(Some(std::usize::MAX)); - sel.track.state.select(Some(1)); - assert_eq!(sel.state.selected(), Some(std::usize::MAX)); - assert_eq!(sel.track.state.selected(), Some(1)); - - sel.increment(&[]); - assert_eq!(sel.state.selected(), Some(std::usize::MAX)); - assert_eq!(sel.track.state.selected(), Some(1)); } #[test] @@ -531,43 +651,101 @@ mod tests { assert!(artists.len() > 1); let empty = ArtistSelection::initialise(&[]); - assert_eq!(empty.state.selected(), None); + assert_eq!(empty.state.list.selected(), None); let mut sel = ArtistSelection::initialise(artists); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.album.state.selected(), Some(0)); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(0)); - sel.increment_album(artists); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.album.state.selected(), Some(1)); + sel.increment_album(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(1)); // Verify that decrement that doesn't change index does not reset album. - sel.decrement(artists); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.album.state.selected(), Some(1)); + sel.decrement(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(1)); - sel.increment(artists); - assert_eq!(sel.state.selected(), Some(1)); - assert_eq!(sel.album.state.selected(), Some(0)); + sel.increment(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(1)); + assert_eq!(sel.album.state.list.selected(), Some(0)); - sel.decrement(artists); - assert_eq!(sel.state.selected(), Some(0)); - assert_eq!(sel.album.state.selected(), Some(0)); + sel.decrement(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(0)); for _ in 0..(artists.len() + 5) { - sel.increment(artists); + sel.increment(artists, Delta::Line); } - assert_eq!(sel.state.selected(), Some(artists.len() - 1)); - assert_eq!(sel.album.state.selected(), Some(0)); + assert_eq!(sel.state.list.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.list.selected(), Some(0)); - sel.increment_album(artists); - assert_eq!(sel.state.selected(), Some(artists.len() - 1)); - assert_eq!(sel.album.state.selected(), Some(1)); + sel.increment_album(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.list.selected(), Some(1)); // Verify that increment that doesn't change index does not reset album. - sel.increment(artists); - assert_eq!(sel.state.selected(), Some(artists.len() - 1)); - assert_eq!(sel.album.state.selected(), Some(1)); + sel.increment(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.list.selected(), Some(1)); + } + + #[test] + fn artist_delta_page() { + let artists = &COLLECTION; + assert!(artists.len() > 1); + + let empty = ArtistSelection::initialise(&[]); + assert_eq!(empty.state.list.selected(), None); + + let mut sel = ArtistSelection::initialise(artists); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(0)); + + assert!(artists.len() >= 4); + sel.state.height = 3; + + sel.increment_album(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(1)); + + // Verify that decrement that doesn't change index does not reset album. + sel.decrement(artists, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(1)); + + sel.increment(artists, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(2)); + assert_eq!(sel.album.state.list.selected(), Some(0)); + + sel.decrement(artists, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(0)); + assert_eq!(sel.album.state.list.selected(), Some(0)); + + for _ in 0..(artists.len() + 5) { + sel.increment(artists, Delta::Page); + } + assert_eq!(sel.state.list.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.list.selected(), Some(0)); + + sel.increment_album(artists, Delta::Line); + assert_eq!(sel.state.list.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.list.selected(), Some(1)); + + // Verify that increment that doesn't change index does not reset album. + sel.increment(artists, Delta::Page); + assert_eq!(sel.state.list.selected(), Some(artists.len() - 1)); + assert_eq!(sel.album.state.list.selected(), Some(1)); + } + + #[test] + fn artist_reinitialise() { + let artists = &COLLECTION; + assert!(artists.len() > 1); + + let mut sel = ArtistSelection::initialise(artists); + sel.state.list.select(Some(artists.len() - 1)); + sel.album.state.list.select(Some(1)); // Re-initialise. let expected = sel.clone(); @@ -577,7 +755,7 @@ mod tests { // Re-initialise out-of-bounds. let mut expected = sel.clone(); - expected.decrement(artists); + expected.decrement(artists, Delta::Line); let active_artist = ActiveArtist::get(artists, &sel); sel.reinitialise(&artists[..(artists.len() - 1)], active_artist); assert_eq!(sel, expected); @@ -587,15 +765,5 @@ mod tests { let active_artist = ActiveArtist::get(artists, &sel); sel.reinitialise(&[], active_artist); assert_eq!(sel, expected); - - // Artifical test case to verify upper limit. - sel.state.select(Some(std::usize::MAX)); - sel.album.state.select(Some(1)); - assert_eq!(sel.state.selected(), Some(std::usize::MAX)); - assert_eq!(sel.album.state.selected(), Some(1)); - - sel.increment(&[]); - assert_eq!(sel.state.selected(), Some(std::usize::MAX)); - assert_eq!(sel.album.state.selected(), Some(1)); } } diff --git a/src/tui/handler.rs b/src/tui/handler.rs index 226d00c..4141a23 100644 --- a/src/tui/handler.rs +++ b/src/tui/handler.rs @@ -4,9 +4,12 @@ use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use mockall::automock; use crate::tui::{ - app::app::{ - AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo, - IAppInteractReload, + app::{ + app::{ + AppState, IAppInteract, IAppInteractBrowse, IAppInteractError, IAppInteractInfo, + IAppInteractReload, + }, + selection::Delta, }, event::{Event, EventError, EventReceiver}, }; @@ -83,8 +86,10 @@ impl IEventHandlerPrivate for EventHandler { KeyCode::Left => app.decrement_category(), KeyCode::Right => app.increment_category(), // Selection change. - KeyCode::Up => app.decrement_selection(), - KeyCode::Down => app.increment_selection(), + KeyCode::Up => app.decrement_selection(Delta::Line), + KeyCode::Down => app.increment_selection(Delta::Line), + KeyCode::PageUp => app.decrement_selection(Delta::Page), + KeyCode::PageDown => app.increment_selection(Delta::Page), // Toggle overlay. KeyCode::Char('m') | KeyCode::Char('M') => app.show_info_overlay(), // Toggle Reload diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 5265afa..e9ba010 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -14,7 +14,7 @@ use ratatui::{ use crate::tui::app::{ app::{AppState, IAppAccess}, - selection::{Category, Selection}, + selection::{Category, Selection, WidgetState}, }; pub trait IUi { @@ -159,11 +159,11 @@ impl OverlayBuilder { struct ArtistState<'a, 'b> { active: bool, list: List<'a>, - state: &'b mut ListState, + state: &'b mut WidgetState, } impl<'a, 'b> ArtistState<'a, 'b> { - fn new(active: bool, artists: &'a [Artist], state: &'b mut ListState) -> ArtistState<'a, 'b> { + fn new(active: bool, artists: &'a [Artist], state: &'b mut WidgetState) -> ArtistState<'a, 'b> { let list = List::new( artists .iter() @@ -234,12 +234,12 @@ impl<'a> ArtistOverlay<'a> { struct AlbumState<'a, 'b> { active: bool, list: List<'a>, - state: &'b mut ListState, + state: &'b mut WidgetState, info: Paragraph<'a>, } impl<'a, 'b> AlbumState<'a, 'b> { - fn new(active: bool, albums: &'a [Album], state: &'b mut ListState) -> AlbumState<'a, 'b> { + fn new(active: bool, albums: &'a [Album], state: &'b mut WidgetState) -> AlbumState<'a, 'b> { let list = List::new( albums .iter() @@ -247,7 +247,7 @@ impl<'a, 'b> AlbumState<'a, 'b> { .collect::>(), ); - let album = state.selected().map(|i| &albums[i]); + let album = state.list.selected().map(|i| &albums[i]); let info = Paragraph::new(format!( "Title: {}\n\ Year: {}", @@ -267,12 +267,12 @@ impl<'a, 'b> AlbumState<'a, 'b> { struct TrackState<'a, 'b> { active: bool, list: List<'a>, - state: &'b mut ListState, + state: &'b mut WidgetState, info: Paragraph<'a>, } impl<'a, 'b> TrackState<'a, 'b> { - fn new(active: bool, tracks: &'a [Track], state: &'b mut ListState) -> TrackState<'a, 'b> { + fn new(active: bool, tracks: &'a [Track], state: &'b mut WidgetState) -> TrackState<'a, 'b> { let list = List::new( tracks .iter() @@ -280,7 +280,7 @@ impl<'a, 'b> TrackState<'a, 'b> { .collect::>(), ); - let track = state.selected().map(|i| &tracks[i]); + let track = state.list.selected().map(|i| &tracks[i]); let info = Paragraph::new(format!( "Track: {}\n\ Title: {}\n\ @@ -342,7 +342,7 @@ impl Ui { fn render_list_widget( title: &str, list: List, - list_state: &mut ListState, + state: &mut WidgetState, active: bool, area: Rect, frame: &mut Frame<'_, B>, @@ -353,8 +353,9 @@ impl Ui { .style(Self::style(active, false)) .block(Self::block(title, active, false)), area, - list_state, + &mut state.list, ); + state.height = area.height.saturating_sub(2) as usize; } fn render_info_widget( @@ -422,6 +423,7 @@ impl Ui { let no_albums: Vec = vec![]; let albums = artist_selection .state + .list .selected() .map(|i| &artists[i].albums) .unwrap_or_else(|| &no_albums); @@ -437,6 +439,7 @@ impl Ui { let no_tracks: Vec = vec![]; let tracks = album_selection .state + .list .selected() .map(|i| &albums[i].tracks) .unwrap_or_else(|| &no_tracks); @@ -458,7 +461,7 @@ impl Ui { let area = OverlayBuilder::default().build(frame.size()); let artist_selection = &mut selection.artist; - let artist_overlay = ArtistOverlay::new(artists, &artist_selection.state); + let artist_overlay = ArtistOverlay::new(artists, &artist_selection.state.list); Self::render_overlay_widget("Artist", artist_overlay.properties, area, false, frame); } @@ -510,7 +513,11 @@ impl IUi for Ui { #[cfg(test)] mod tests { - use crate::tui::{app::app::AppPublic, testmod::COLLECTION, tests::terminal}; + use crate::tui::{ + app::{app::AppPublic, selection::Delta}, + testmod::COLLECTION, + tests::terminal, + }; use super::*; @@ -564,14 +571,14 @@ mod tests { // Change the track (which has a different track format). selection.increment_category(); selection.increment_category(); - selection.increment_selection(artists); + selection.increment_selection(artists, Delta::Line); draw_test_suite(artists, &mut selection); // Change the artist (which has a multi-link entry). selection.decrement_category(); selection.decrement_category(); - selection.increment_selection(artists); + selection.increment_selection(artists, Delta::Line); draw_test_suite(artists, &mut selection); }