2023-04-27 19:05:37 +02:00
|
|
|
use musichoard::{
|
|
|
|
collection::{Collection, CollectionManager},
|
|
|
|
Album, Artist, Track, TrackFormat,
|
|
|
|
};
|
2023-04-13 14:09:59 +02:00
|
|
|
use ratatui::{
|
|
|
|
backend::Backend,
|
|
|
|
layout::{Alignment, Rect},
|
|
|
|
style::{Color, Style},
|
|
|
|
widgets::{Block, BorderType, Borders, List, ListItem, ListState, Paragraph},
|
|
|
|
Frame,
|
|
|
|
};
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
use super::Error;
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct TrackSelection {
|
|
|
|
state: ListState,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct AlbumSelection {
|
|
|
|
state: ListState,
|
|
|
|
track: TrackSelection,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct ArtistSelection {
|
|
|
|
state: ListState,
|
|
|
|
album: AlbumSelection,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
impl TrackSelection {
|
|
|
|
fn initialise(tracks: Option<&[Track]>) -> Self {
|
|
|
|
let mut state = ListState::default();
|
|
|
|
if let Some(tracks) = tracks {
|
|
|
|
state.select(if !tracks.is_empty() { Some(0) } else { None });
|
|
|
|
} else {
|
|
|
|
state.select(None);
|
|
|
|
};
|
|
|
|
TrackSelection { state }
|
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
impl AlbumSelection {
|
|
|
|
fn initialise(albums: Option<&[Album]>) -> Self {
|
|
|
|
let mut state = ListState::default();
|
|
|
|
let track: TrackSelection;
|
|
|
|
if let Some(albums) = albums {
|
|
|
|
state.select(if !albums.is_empty() { Some(0) } else { None });
|
|
|
|
track = TrackSelection::initialise(albums.get(0).map(|a| a.tracks.as_slice()));
|
|
|
|
} else {
|
|
|
|
state.select(None);
|
|
|
|
track = TrackSelection::initialise(None);
|
|
|
|
}
|
|
|
|
AlbumSelection { state, 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(Some(&albums[result].tracks));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
self.track = TrackSelection::initialise(Some(&albums[result].tracks));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_track(&mut self, albums: &[Album]) {
|
|
|
|
if let Some(index) = self.state.selected() {
|
|
|
|
self.track.decrement(&albums[index].tracks);
|
|
|
|
}
|
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
impl ArtistSelection {
|
|
|
|
fn initialise(artists: Option<&[Artist]>) -> Self {
|
|
|
|
let mut state = ListState::default();
|
|
|
|
let album: AlbumSelection;
|
|
|
|
if let Some(artists) = artists {
|
|
|
|
state.select(if !artists.is_empty() { Some(0) } else { None });
|
|
|
|
album = AlbumSelection::initialise(artists.get(0).map(|a| a.albums.as_slice()));
|
|
|
|
} else {
|
|
|
|
state.select(None);
|
|
|
|
album = AlbumSelection::initialise(None);
|
|
|
|
}
|
|
|
|
ArtistSelection { state, 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(Some(&artists[result].albums));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
self.album = AlbumSelection::initialise(Some(&artists[result].albums));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_album(&mut self, artists: &[Artist]) {
|
|
|
|
if let Some(index) = self.state.selected() {
|
|
|
|
self.album.decrement(&artists[index].albums);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_track(&mut self, artists: &[Artist]) {
|
|
|
|
if let Some(index) = self.state.selected() {
|
|
|
|
self.album.decrement_track(&artists[index].albums);
|
|
|
|
}
|
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum Category {
|
|
|
|
Artist,
|
|
|
|
Album,
|
|
|
|
Track,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct Selection {
|
|
|
|
active: Category,
|
|
|
|
artist: ArtistSelection,
|
2023-04-13 15:29:14 +02:00
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
impl Selection {
|
|
|
|
fn new(artists: Option<&[Artist]>) -> Self {
|
|
|
|
Selection {
|
|
|
|
active: Category::Artist,
|
|
|
|
artist: ArtistSelection::initialise(artists),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_category(&mut self) {
|
|
|
|
self.active = match self.active {
|
|
|
|
Category::Artist => Category::Album,
|
|
|
|
Category::Album => Category::Track,
|
|
|
|
Category::Track => Category::Track,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_category(&mut self) {
|
|
|
|
self.active = match self.active {
|
|
|
|
Category::Artist => Category::Artist,
|
|
|
|
Category::Album => Category::Artist,
|
|
|
|
Category::Track => Category::Album,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_selection(&mut self, collection: &Collection) {
|
|
|
|
match self.active {
|
|
|
|
Category::Artist => self.increment_artist(collection),
|
|
|
|
Category::Album => self.increment_album(collection),
|
|
|
|
Category::Track => self.increment_track(collection),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_selection(&mut self, collection: &Collection) {
|
|
|
|
match self.active {
|
|
|
|
Category::Artist => self.decrement_artist(collection),
|
|
|
|
Category::Album => self.decrement_album(collection),
|
|
|
|
Category::Track => self.decrement_track(collection),
|
2023-04-13 15:29:14 +02:00
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
fn increment_artist(&mut self, artists: &[Artist]) {
|
|
|
|
self.artist.increment(artists);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_artist(&mut self, artists: &[Artist]) {
|
|
|
|
self.artist.decrement(artists);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_album(&mut self, artists: &[Artist]) {
|
|
|
|
self.artist.increment_album(artists);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_album(&mut self, artists: &[Artist]) {
|
|
|
|
self.artist.decrement_album(artists);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn increment_track(&mut self, artists: &[Artist]) {
|
|
|
|
self.artist.increment_track(artists);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn decrement_track(&mut self, artists: &[Artist]) {
|
|
|
|
self.artist.decrement_track(artists);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct MhUi<CM> {
|
|
|
|
collection_manager: CM,
|
|
|
|
selection: Selection,
|
|
|
|
running: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ArtistArea {
|
|
|
|
list: Rect,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct AlbumArea {
|
|
|
|
list: Rect,
|
|
|
|
info: Rect,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct TrackArea {
|
|
|
|
list: Rect,
|
|
|
|
info: Rect,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct FrameArea {
|
|
|
|
artist: ArtistArea,
|
|
|
|
album: AlbumArea,
|
|
|
|
track: TrackArea,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FrameArea {
|
|
|
|
fn new(frame: Rect) -> FrameArea {
|
2023-04-13 14:09:59 +02:00
|
|
|
let width_one_third = frame.width / 3;
|
|
|
|
let height_one_third = frame.height / 3;
|
|
|
|
|
|
|
|
let panel_width = width_one_third;
|
|
|
|
let panel_width_last = frame.width - 2 * panel_width;
|
|
|
|
let panel_height_top = frame.height - height_one_third;
|
|
|
|
let panel_height_bottom = height_one_third;
|
|
|
|
|
|
|
|
let artist_list = Rect {
|
|
|
|
x: frame.x,
|
|
|
|
y: frame.y,
|
|
|
|
width: panel_width,
|
|
|
|
height: frame.height,
|
|
|
|
};
|
|
|
|
|
|
|
|
let album_list = Rect {
|
|
|
|
x: artist_list.x + artist_list.width,
|
|
|
|
y: frame.y,
|
|
|
|
width: panel_width,
|
|
|
|
height: panel_height_top,
|
|
|
|
};
|
|
|
|
|
|
|
|
let album_info = Rect {
|
|
|
|
x: album_list.x,
|
|
|
|
y: album_list.y + album_list.height,
|
|
|
|
width: album_list.width,
|
|
|
|
height: panel_height_bottom,
|
|
|
|
};
|
|
|
|
|
|
|
|
let track_list = Rect {
|
|
|
|
x: album_list.x + album_list.width,
|
|
|
|
y: frame.y,
|
|
|
|
width: panel_width_last,
|
|
|
|
height: panel_height_top,
|
|
|
|
};
|
|
|
|
|
|
|
|
let track_info = Rect {
|
|
|
|
x: track_list.x,
|
|
|
|
y: track_list.y + track_list.height,
|
|
|
|
width: track_list.width,
|
|
|
|
height: panel_height_bottom,
|
|
|
|
};
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
FrameArea {
|
|
|
|
artist: ArtistArea { list: artist_list },
|
|
|
|
album: AlbumArea {
|
2023-04-13 14:09:59 +02:00
|
|
|
list: album_list,
|
|
|
|
info: album_info,
|
|
|
|
},
|
2023-04-27 19:05:37 +02:00
|
|
|
track: TrackArea {
|
2023-04-13 14:09:59 +02:00
|
|
|
list: track_list,
|
|
|
|
info: track_info,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-04-27 19:05:37 +02:00
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct ArtistState<'a, 'b> {
|
|
|
|
active: bool,
|
|
|
|
list: List<'a>,
|
|
|
|
state: &'b mut ListState,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> ArtistState<'a, 'b> {
|
|
|
|
fn new(active: bool, artists: &'a [Artist], state: &'b mut ListState) -> ArtistState<'a, 'b> {
|
2023-04-13 14:09:59 +02:00
|
|
|
let list = List::new(
|
|
|
|
artists
|
|
|
|
.iter()
|
2023-04-27 19:05:37 +02:00
|
|
|
.map(|a| ListItem::new(a.id.name.as_str()))
|
2023-04-13 14:09:59 +02:00
|
|
|
.collect::<Vec<ListItem>>(),
|
|
|
|
);
|
|
|
|
|
|
|
|
ArtistState {
|
|
|
|
active,
|
2023-04-27 19:05:37 +02:00
|
|
|
list,
|
|
|
|
state,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
}
|
2023-04-27 19:05:37 +02:00
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct AlbumState<'a, 'b> {
|
|
|
|
active: bool,
|
|
|
|
list: List<'a>,
|
|
|
|
state: &'b mut ListState,
|
|
|
|
info: Paragraph<'a>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> AlbumState<'a, 'b> {
|
|
|
|
fn new(active: bool, albums: &'a [Album], state: &'b mut ListState) -> AlbumState<'a, 'b> {
|
2023-04-13 14:09:59 +02:00
|
|
|
let list = List::new(
|
|
|
|
albums
|
|
|
|
.iter()
|
2023-04-27 19:05:37 +02:00
|
|
|
.map(|a| ListItem::new(a.id.title.as_str()))
|
2023-04-13 14:09:59 +02:00
|
|
|
.collect::<Vec<ListItem>>(),
|
|
|
|
);
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
let album = state.selected().map(|i| &albums[i]);
|
2023-04-13 14:09:59 +02:00
|
|
|
let info = Paragraph::new(format!(
|
|
|
|
"Title: {}\n\
|
|
|
|
Year: {}",
|
2023-04-27 19:05:37 +02:00
|
|
|
album.map(|a| a.id.title.as_str()).unwrap_or(""),
|
2023-04-13 14:09:59 +02:00
|
|
|
album
|
2023-04-27 19:05:37 +02:00
|
|
|
.map(|a| a.id.year.to_string())
|
2023-04-13 14:09:59 +02:00
|
|
|
.unwrap_or_else(|| "".to_string()),
|
|
|
|
));
|
|
|
|
|
|
|
|
AlbumState {
|
|
|
|
active,
|
2023-04-27 19:05:37 +02:00
|
|
|
list,
|
|
|
|
state,
|
|
|
|
info,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
}
|
2023-04-27 19:05:37 +02:00
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
struct TrackState<'a, 'b> {
|
|
|
|
active: bool,
|
|
|
|
list: List<'a>,
|
|
|
|
state: &'b mut ListState,
|
|
|
|
info: Paragraph<'a>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, 'b> TrackState<'a, 'b> {
|
|
|
|
fn new(active: bool, tracks: &'a [Track], state: &'b mut ListState) -> TrackState<'a, 'b> {
|
2023-04-13 14:09:59 +02:00
|
|
|
let list = List::new(
|
|
|
|
tracks
|
|
|
|
.iter()
|
|
|
|
.map(|id| ListItem::new(id.title.as_str()))
|
|
|
|
.collect::<Vec<ListItem>>(),
|
|
|
|
);
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
let track = state.selected().map(|i| &tracks[i]);
|
2023-04-13 14:09:59 +02:00
|
|
|
let info = Paragraph::new(format!(
|
|
|
|
"Track: {}\n\
|
|
|
|
Title: {}\n\
|
|
|
|
Artist: {}\n\
|
|
|
|
Format: {}",
|
|
|
|
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 {
|
|
|
|
active,
|
2023-04-27 19:05:37 +02:00
|
|
|
list,
|
|
|
|
state,
|
|
|
|
info,
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
}
|
2023-04-27 19:05:37 +02:00
|
|
|
}
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
impl<CM: CollectionManager> MhUi<CM> {
|
|
|
|
pub fn new(mut collection_manager: CM) -> Result<Self, Error> {
|
|
|
|
collection_manager.rescan_library()?;
|
|
|
|
let selection = Selection::new(Some(collection_manager.get_collection()));
|
|
|
|
Ok(MhUi {
|
|
|
|
collection_manager,
|
|
|
|
selection,
|
|
|
|
running: true,
|
|
|
|
})
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
if active {
|
|
|
|
Style::default().fg(Color::White).bg(Color::DarkGray)
|
|
|
|
} else {
|
|
|
|
Self::style(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn block<'a>(title: &str, active: bool) -> Block<'a> {
|
|
|
|
Block::default()
|
|
|
|
.title_alignment(Alignment::Center)
|
|
|
|
.borders(Borders::ALL)
|
|
|
|
.border_type(BorderType::Rounded)
|
|
|
|
.style(Self::block_style(active))
|
|
|
|
.title(format!(" {title} "))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render_list_widget<B: Backend>(
|
|
|
|
title: &str,
|
2023-04-27 19:05:37 +02:00
|
|
|
list: List,
|
|
|
|
list_state: &mut ListState,
|
2023-04-13 14:09:59 +02:00
|
|
|
active: bool,
|
|
|
|
area: Rect,
|
|
|
|
frame: &mut Frame<'_, B>,
|
|
|
|
) {
|
|
|
|
frame.render_stateful_widget(
|
2023-04-27 19:05:37 +02:00
|
|
|
list.highlight_style(Self::highlight_style(active))
|
2023-04-13 14:09:59 +02:00
|
|
|
.highlight_symbol(">> ")
|
|
|
|
.style(Self::style(active))
|
|
|
|
.block(Self::block(title, active)),
|
|
|
|
area,
|
2023-04-27 19:05:37 +02:00
|
|
|
list_state,
|
2023-04-13 14:09:59 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render_info_widget<B: Backend>(
|
|
|
|
title: &str,
|
|
|
|
paragraph: Paragraph,
|
|
|
|
active: bool,
|
|
|
|
area: Rect,
|
|
|
|
frame: &mut Frame<'_, B>,
|
|
|
|
) {
|
|
|
|
frame.render_widget(
|
|
|
|
paragraph
|
|
|
|
.style(Self::style(active))
|
|
|
|
.block(Self::block(title, active)),
|
|
|
|
area,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
fn render_artist_column<B: Backend>(st: ArtistState, ar: ArtistArea, fr: &mut Frame<'_, B>) {
|
|
|
|
Self::render_list_widget("Artists", st.list, st.state, st.active, ar.list, fr);
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
fn render_album_column<B: Backend>(st: AlbumState, ar: AlbumArea, fr: &mut Frame<'_, B>) {
|
|
|
|
Self::render_list_widget("Albums", st.list, st.state, st.active, ar.list, fr);
|
|
|
|
Self::render_info_widget("Album info", st.info, st.active, ar.info, fr);
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
fn render_track_column<B: Backend>(st: TrackState, ar: TrackArea, fr: &mut Frame<'_, B>) {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Ui {
|
|
|
|
fn is_running(&self) -> bool;
|
|
|
|
fn quit(&mut self);
|
|
|
|
|
|
|
|
fn increment_category(&mut self);
|
|
|
|
fn decrement_category(&mut self);
|
|
|
|
|
|
|
|
fn increment_selection(&mut self);
|
|
|
|
fn decrement_selection(&mut self);
|
|
|
|
|
|
|
|
fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>);
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<CM: CollectionManager> Ui for MhUi<CM> {
|
|
|
|
fn is_running(&self) -> bool {
|
|
|
|
self.running
|
|
|
|
}
|
|
|
|
|
|
|
|
fn quit(&mut self) {
|
|
|
|
self.running = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.collection_manager.get_collection());
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
fn decrement_selection(&mut self) {
|
|
|
|
self.selection
|
|
|
|
.decrement_selection(self.collection_manager.get_collection());
|
|
|
|
}
|
|
|
|
|
|
|
|
fn render<B: Backend>(&mut self, frame: &mut Frame<'_, B>) {
|
|
|
|
let active = self.selection.active;
|
|
|
|
let areas = FrameArea::new(frame.size());
|
|
|
|
|
|
|
|
let artists = self.collection_manager.get_collection();
|
|
|
|
let artist_selection = &mut self.selection.artist;
|
|
|
|
let artist_state = ArtistState::new(
|
|
|
|
active == Category::Artist,
|
|
|
|
artists,
|
|
|
|
&mut artist_selection.state,
|
|
|
|
);
|
|
|
|
|
|
|
|
Self::render_artist_column(artist_state, areas.artist, frame);
|
|
|
|
|
|
|
|
let no_albums: Vec<Album> = vec![];
|
|
|
|
let albums = artist_selection
|
|
|
|
.state
|
|
|
|
.selected()
|
|
|
|
.map(|i| &artists[i].albums)
|
|
|
|
.unwrap_or_else(|| &no_albums);
|
|
|
|
let album_selection = &mut artist_selection.album;
|
|
|
|
let album_state = AlbumState::new(
|
|
|
|
active == Category::Album,
|
|
|
|
albums,
|
|
|
|
&mut album_selection.state,
|
|
|
|
);
|
|
|
|
|
|
|
|
Self::render_album_column(album_state, areas.album, frame);
|
|
|
|
|
|
|
|
let no_tracks: Vec<Track> = vec![];
|
|
|
|
let tracks = album_selection
|
|
|
|
.state
|
|
|
|
.selected()
|
|
|
|
.map(|i| &albums[i].tracks)
|
|
|
|
.unwrap_or_else(|| &no_tracks);
|
|
|
|
let track_selection = &mut album_selection.track;
|
|
|
|
let track_state = TrackState::new(
|
|
|
|
active == Category::Track,
|
|
|
|
tracks,
|
|
|
|
&mut track_selection.state,
|
|
|
|
);
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
Self::render_track_column(track_state, areas.track, frame);
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-04-27 19:05:37 +02:00
|
|
|
use crate::tests::{MockCollectionManager, COLLECTION};
|
|
|
|
use crate::tui::tests::{terminal, ui};
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_track_selection() {
|
|
|
|
let tracks = &COLLECTION[0].albums[0].tracks;
|
|
|
|
assert!(tracks.len() > 1);
|
|
|
|
|
|
|
|
let empty = TrackSelection::initialise(None);
|
|
|
|
assert_eq!(empty.state.selected(), None);
|
|
|
|
|
|
|
|
let empty = TrackSelection::initialise(Some(&vec![]));
|
|
|
|
assert_eq!(empty.state.selected(), None);
|
|
|
|
|
|
|
|
let mut sel = TrackSelection::initialise(Some(tracks));
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.decrement(tracks);
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.increment(tracks);
|
|
|
|
assert_eq!(sel.state.selected(), Some(1));
|
|
|
|
|
|
|
|
sel.decrement(tracks);
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
|
|
|
|
for _ in 0..(tracks.len() + 5) {
|
|
|
|
sel.increment(tracks);
|
|
|
|
}
|
|
|
|
assert_eq!(sel.state.selected(), Some(tracks.len() - 1));
|
|
|
|
|
|
|
|
// 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(&vec![]);
|
|
|
|
assert_eq!(sel.state.selected(), Some(std::usize::MAX));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_album_selection() {
|
|
|
|
let albums = &COLLECTION[0].albums;
|
|
|
|
assert!(albums.len() > 1);
|
|
|
|
|
|
|
|
let empty = AlbumSelection::initialise(None);
|
|
|
|
assert_eq!(empty.state.selected(), None);
|
|
|
|
|
|
|
|
let empty = AlbumSelection::initialise(Some(&vec![]));
|
|
|
|
assert_eq!(empty.state.selected(), None);
|
|
|
|
|
|
|
|
let mut sel = AlbumSelection::initialise(Some(albums));
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
assert_eq!(sel.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.increment_track(albums);
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
assert_eq!(sel.track.state.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.increment(albums);
|
|
|
|
assert_eq!(sel.state.selected(), Some(1));
|
|
|
|
assert_eq!(sel.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.decrement(albums);
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
assert_eq!(sel.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
for _ in 0..(albums.len() + 5) {
|
|
|
|
sel.increment(albums);
|
|
|
|
}
|
|
|
|
assert_eq!(sel.state.selected(), Some(albums.len() - 1));
|
|
|
|
assert_eq!(sel.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.increment_track(albums);
|
|
|
|
assert_eq!(sel.state.selected(), Some(albums.len() - 1));
|
|
|
|
assert_eq!(sel.track.state.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));
|
|
|
|
|
|
|
|
// 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(&vec![]);
|
|
|
|
assert_eq!(sel.state.selected(), Some(std::usize::MAX));
|
|
|
|
assert_eq!(sel.track.state.selected(), Some(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_artist_selection() {
|
|
|
|
let artists = &COLLECTION;
|
|
|
|
assert!(artists.len() > 1);
|
|
|
|
|
|
|
|
let empty = ArtistSelection::initialise(None);
|
|
|
|
assert_eq!(empty.state.selected(), None);
|
|
|
|
|
|
|
|
let empty = ArtistSelection::initialise(Some(&vec![]));
|
|
|
|
assert_eq!(empty.state.selected(), None);
|
|
|
|
|
|
|
|
let mut sel = ArtistSelection::initialise(Some(artists));
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
assert_eq!(sel.album.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.increment_album(artists);
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
assert_eq!(sel.album.state.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.increment(artists);
|
|
|
|
assert_eq!(sel.state.selected(), Some(1));
|
|
|
|
assert_eq!(sel.album.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.decrement(artists);
|
|
|
|
assert_eq!(sel.state.selected(), Some(0));
|
|
|
|
assert_eq!(sel.album.state.selected(), Some(0));
|
|
|
|
|
|
|
|
for _ in 0..(artists.len() + 5) {
|
|
|
|
sel.increment(artists);
|
|
|
|
}
|
|
|
|
assert_eq!(sel.state.selected(), Some(artists.len() - 1));
|
|
|
|
assert_eq!(sel.album.state.selected(), Some(0));
|
|
|
|
|
|
|
|
sel.increment_album(artists);
|
|
|
|
assert_eq!(sel.state.selected(), Some(artists.len() - 1));
|
|
|
|
assert_eq!(sel.album.state.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));
|
|
|
|
|
|
|
|
// 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(&vec![]);
|
|
|
|
assert_eq!(sel.state.selected(), Some(std::usize::MAX));
|
|
|
|
assert_eq!(sel.album.state.selected(), Some(1));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ui_running() {
|
|
|
|
let mut collection_manager = MockCollectionManager::new();
|
|
|
|
|
|
|
|
collection_manager
|
|
|
|
.expect_rescan_library()
|
|
|
|
.times(1)
|
|
|
|
.return_once(|| Ok(()));
|
|
|
|
collection_manager
|
|
|
|
.expect_get_collection()
|
|
|
|
.return_const(COLLECTION.to_owned());
|
|
|
|
|
|
|
|
let mut ui = MhUi::new(collection_manager).unwrap();
|
|
|
|
assert!(ui.is_running());
|
|
|
|
|
|
|
|
ui.quit();
|
|
|
|
assert!(!ui.is_running());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn ui_modifiers() {
|
|
|
|
let mut collection_manager = MockCollectionManager::new();
|
|
|
|
|
|
|
|
collection_manager
|
|
|
|
.expect_rescan_library()
|
|
|
|
.times(1)
|
|
|
|
.return_once(|| Ok(()));
|
|
|
|
collection_manager
|
|
|
|
.expect_get_collection()
|
|
|
|
.return_const(COLLECTION.to_owned());
|
|
|
|
|
|
|
|
let mut ui = MhUi::new(collection_manager).unwrap();
|
|
|
|
assert!(ui.is_running());
|
|
|
|
|
|
|
|
assert_eq!(ui.selection.active, Category::Artist);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_selection();
|
|
|
|
assert_eq!(ui.selection.active, Category::Artist);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_category();
|
|
|
|
assert_eq!(ui.selection.active, Category::Album);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_selection();
|
|
|
|
assert_eq!(ui.selection.active, Category::Album);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_category();
|
|
|
|
assert_eq!(ui.selection.active, Category::Track);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_selection();
|
|
|
|
assert_eq!(ui.selection.active, Category::Track);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(1));
|
|
|
|
|
|
|
|
ui.increment_category();
|
|
|
|
assert_eq!(ui.selection.active, Category::Track);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(1));
|
|
|
|
|
|
|
|
ui.decrement_selection();
|
|
|
|
assert_eq!(ui.selection.active, Category::Track);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
ui.increment_selection();
|
|
|
|
ui.decrement_category();
|
|
|
|
assert_eq!(ui.selection.active, Category::Album);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(1));
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
ui.decrement_selection();
|
|
|
|
assert_eq!(ui.selection.active, Category::Album);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_selection();
|
|
|
|
ui.decrement_category();
|
|
|
|
assert_eq!(ui.selection.active, Category::Artist);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.decrement_selection();
|
|
|
|
assert_eq!(ui.selection.active, Category::Artist);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
|
|
|
|
ui.increment_category();
|
|
|
|
ui.increment_selection();
|
|
|
|
ui.decrement_category();
|
|
|
|
ui.decrement_selection();
|
|
|
|
ui.decrement_category();
|
|
|
|
assert_eq!(ui.selection.active, Category::Artist);
|
|
|
|
assert_eq!(ui.selection.artist.state.selected(), Some(0));
|
|
|
|
assert_eq!(ui.selection.artist.album.state.selected(), Some(1));
|
|
|
|
assert_eq!(ui.selection.artist.album.track.state.selected(), Some(0));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn app_no_tracks() {
|
|
|
|
let mut collection_manager = MockCollectionManager::new();
|
|
|
|
let mut collection = COLLECTION.to_owned();
|
|
|
|
collection[0].albums[0].tracks = vec![];
|
|
|
|
|
|
|
|
collection_manager
|
|
|
|
.expect_rescan_library()
|
|
|
|
.times(1)
|
|
|
|
.return_once(|| Ok(()));
|
|
|
|
collection_manager
|
|
|
|
.expect_get_collection()
|
|
|
|
.return_const(collection);
|
|
|
|
|
|
|
|
let mut app = MhUi::new(collection_manager).unwrap();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.increment_category();
|
|
|
|
app.increment_category();
|
|
|
|
|
|
|
|
app.increment_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.decrement_selection();
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn app_no_albums() {
|
|
|
|
let mut collection_manager = MockCollectionManager::new();
|
|
|
|
let mut collection = COLLECTION.to_owned();
|
|
|
|
collection[0].albums = vec![];
|
|
|
|
|
|
|
|
collection_manager
|
|
|
|
.expect_rescan_library()
|
|
|
|
.times(1)
|
|
|
|
.return_once(|| Ok(()));
|
|
|
|
collection_manager
|
|
|
|
.expect_get_collection()
|
|
|
|
.return_const(collection);
|
|
|
|
|
|
|
|
let mut app = MhUi::new(collection_manager).unwrap();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.increment_category();
|
|
|
|
|
|
|
|
app.increment_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.decrement_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.increment_category();
|
|
|
|
|
|
|
|
app.increment_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.decrement_selection();
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn app_no_artists() {
|
|
|
|
let mut collection_manager = MockCollectionManager::new();
|
|
|
|
let collection = vec![];
|
|
|
|
|
|
|
|
collection_manager
|
|
|
|
.expect_rescan_library()
|
|
|
|
.times(1)
|
|
|
|
.return_once(|| Ok(()));
|
|
|
|
collection_manager
|
|
|
|
.expect_get_collection()
|
|
|
|
.return_const(collection);
|
|
|
|
|
|
|
|
let mut app = MhUi::new(collection_manager).unwrap();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.increment_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.decrement_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.increment_category();
|
|
|
|
|
|
|
|
app.increment_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.decrement_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.increment_category();
|
|
|
|
|
|
|
|
app.increment_selection();
|
|
|
|
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);
|
|
|
|
|
|
|
|
app.decrement_selection();
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is UI so the only sensible unit test is to run the code through various app states.
|
2023-04-13 14:09:59 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn empty() {
|
|
|
|
let mut terminal = terminal();
|
2023-04-27 19:05:37 +02:00
|
|
|
let mut ui = ui(vec![]);
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
terminal.draw(|frame| ui.render(frame)).unwrap();
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn collection() {
|
|
|
|
let mut terminal = terminal();
|
2023-04-27 19:05:37 +02:00
|
|
|
let mut ui = ui(COLLECTION.to_owned());
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
terminal.draw(|frame| ui.render(frame)).unwrap();
|
2023-04-13 14:09:59 +02:00
|
|
|
|
|
|
|
// Change the track (which has a different track format).
|
2023-04-27 19:05:37 +02:00
|
|
|
ui.increment_category();
|
|
|
|
ui.increment_category();
|
|
|
|
ui.increment_selection();
|
2023-04-13 14:09:59 +02:00
|
|
|
|
2023-04-27 19:05:37 +02:00
|
|
|
terminal.draw(|frame| ui.render(frame)).unwrap();
|
2023-04-13 14:09:59 +02:00
|
|
|
}
|
|
|
|
}
|