Extract browse
This commit is contained in:
parent
5a446ad4b8
commit
0f049e40ee
233
src/tui/ui/browse.rs
Normal file
233
src/tui/ui/browse.rs
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
use musichoard::collection::{
|
||||||
|
album::{Album, AlbumStatus},
|
||||||
|
artist::Artist,
|
||||||
|
track::{Track, TrackFormat},
|
||||||
|
};
|
||||||
|
use ratatui::{
|
||||||
|
layout::Rect,
|
||||||
|
text::Line,
|
||||||
|
widgets::{List, ListItem, Paragraph},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::tui::app::WidgetState;
|
||||||
|
|
||||||
|
use super::{UiColor, UiDisplay};
|
||||||
|
|
||||||
|
pub struct ArtistArea {
|
||||||
|
pub list: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AlbumArea {
|
||||||
|
pub list: Rect,
|
||||||
|
pub info: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TrackArea {
|
||||||
|
pub list: Rect,
|
||||||
|
pub info: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrameArea {
|
||||||
|
pub artist: ArtistArea,
|
||||||
|
pub album: AlbumArea,
|
||||||
|
pub track: TrackArea,
|
||||||
|
pub minibuffer: Rect,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FrameArea {
|
||||||
|
pub fn new(frame: Rect) -> Self {
|
||||||
|
let minibuffer_height = 3;
|
||||||
|
let buffer_height = frame.height.saturating_sub(minibuffer_height);
|
||||||
|
|
||||||
|
let width_one_third = frame.width / 3;
|
||||||
|
let height_one_third = buffer_height / 3;
|
||||||
|
|
||||||
|
let panel_width = width_one_third;
|
||||||
|
let panel_width_last = frame.width.saturating_sub(2 * panel_width);
|
||||||
|
let panel_height_top = buffer_height.saturating_sub(height_one_third);
|
||||||
|
let panel_height_bottom = height_one_third;
|
||||||
|
|
||||||
|
let artist_list = Rect {
|
||||||
|
x: frame.x,
|
||||||
|
y: frame.y,
|
||||||
|
width: panel_width,
|
||||||
|
height: buffer_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,
|
||||||
|
};
|
||||||
|
|
||||||
|
let minibuffer = Rect {
|
||||||
|
x: frame.x,
|
||||||
|
y: frame.y + buffer_height,
|
||||||
|
width: frame.width,
|
||||||
|
height: minibuffer_height,
|
||||||
|
};
|
||||||
|
|
||||||
|
FrameArea {
|
||||||
|
artist: ArtistArea { list: artist_list },
|
||||||
|
album: AlbumArea {
|
||||||
|
list: album_list,
|
||||||
|
info: album_info,
|
||||||
|
},
|
||||||
|
track: TrackArea {
|
||||||
|
list: track_list,
|
||||||
|
info: track_info,
|
||||||
|
},
|
||||||
|
minibuffer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ArtistState<'a, 'b> {
|
||||||
|
pub active: bool,
|
||||||
|
pub list: List<'a>,
|
||||||
|
pub state: &'b mut WidgetState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> ArtistState<'a, 'b> {
|
||||||
|
pub fn new(
|
||||||
|
active: bool,
|
||||||
|
artists: &'a [Artist],
|
||||||
|
state: &'b mut WidgetState,
|
||||||
|
) -> ArtistState<'a, 'b> {
|
||||||
|
let list = List::new(
|
||||||
|
artists
|
||||||
|
.iter()
|
||||||
|
.map(|a| ListItem::new(a.id.name.as_str()))
|
||||||
|
.collect::<Vec<ListItem>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
ArtistState {
|
||||||
|
active,
|
||||||
|
list,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AlbumState<'a, 'b> {
|
||||||
|
pub active: bool,
|
||||||
|
pub list: List<'a>,
|
||||||
|
pub state: &'b mut WidgetState,
|
||||||
|
pub info: Paragraph<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> AlbumState<'a, 'b> {
|
||||||
|
pub fn new(
|
||||||
|
active: bool,
|
||||||
|
albums: &'a [Album],
|
||||||
|
state: &'b mut WidgetState,
|
||||||
|
) -> AlbumState<'a, 'b> {
|
||||||
|
let list = List::new(
|
||||||
|
albums
|
||||||
|
.iter()
|
||||||
|
.map(Self::to_list_item)
|
||||||
|
.collect::<Vec<ListItem>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let album = state.list.selected().map(|i| &albums[i]);
|
||||||
|
let info = Paragraph::new(format!(
|
||||||
|
"Title: {}\n\
|
||||||
|
Date: {}\n\
|
||||||
|
Type: {}\n\
|
||||||
|
Status: {}",
|
||||||
|
album.map(|a| a.id.title.as_str()).unwrap_or(""),
|
||||||
|
album
|
||||||
|
.map(|a| UiDisplay::display_date(&a.date, &a.seq))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
album
|
||||||
|
.map(|a| UiDisplay::display_type(&a.primary_type, &a.secondary_types))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
album
|
||||||
|
.map(|a| UiDisplay::display_album_status(&a.get_status()))
|
||||||
|
.unwrap_or("")
|
||||||
|
));
|
||||||
|
|
||||||
|
AlbumState {
|
||||||
|
active,
|
||||||
|
list,
|
||||||
|
state,
|
||||||
|
info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_list_item(album: &Album) -> ListItem {
|
||||||
|
let line = match album.get_status() {
|
||||||
|
AlbumStatus::None => Line::raw(album.id.title.as_str()),
|
||||||
|
AlbumStatus::Owned(format) => match format {
|
||||||
|
TrackFormat::Mp3 => Line::styled(album.id.title.as_str(), UiColor::FG_WARN),
|
||||||
|
TrackFormat::Flac => Line::styled(album.id.title.as_str(), UiColor::FG_GOOD),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
ListItem::new(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TrackState<'a, 'b> {
|
||||||
|
pub active: bool,
|
||||||
|
pub list: List<'a>,
|
||||||
|
pub state: &'b mut WidgetState,
|
||||||
|
pub info: Paragraph<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> TrackState<'a, 'b> {
|
||||||
|
pub fn new(
|
||||||
|
active: bool,
|
||||||
|
tracks: &'a [Track],
|
||||||
|
state: &'b mut WidgetState,
|
||||||
|
) -> TrackState<'a, 'b> {
|
||||||
|
let list = List::new(
|
||||||
|
tracks
|
||||||
|
.iter()
|
||||||
|
.map(|tr| ListItem::new(tr.id.title.as_str()))
|
||||||
|
.collect::<Vec<ListItem>>(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let track = state.list.selected().map(|i| &tracks[i]);
|
||||||
|
let info = Paragraph::new(format!(
|
||||||
|
"Track: {}\n\
|
||||||
|
Title: {}\n\
|
||||||
|
Artist: {}\n\
|
||||||
|
Quality: {}",
|
||||||
|
track.map(|t| t.number.0.to_string()).unwrap_or_default(),
|
||||||
|
track.map(|t| t.id.title.as_str()).unwrap_or(""),
|
||||||
|
track.map(|t| t.artist.join("; ")).unwrap_or_default(),
|
||||||
|
track
|
||||||
|
.map(|t| UiDisplay::display_track_quality(&t.quality))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
TrackState {
|
||||||
|
active,
|
||||||
|
list,
|
||||||
|
state,
|
||||||
|
info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
|
mod browse;
|
||||||
mod minibuffer;
|
mod minibuffer;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use browse::{AlbumArea, AlbumState, ArtistArea, ArtistState, FrameArea, TrackArea, TrackState};
|
||||||
use minibuffer::Minibuffer;
|
use minibuffer::Minibuffer;
|
||||||
use musichoard::collection::{
|
use musichoard::collection::{
|
||||||
album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq, AlbumStatus},
|
album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq, AlbumStatus},
|
||||||
@ -13,7 +15,6 @@ use musichoard::collection::{
|
|||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Rect},
|
layout::{Alignment, Rect},
|
||||||
style::{Color, Style},
|
style::{Color, Style},
|
||||||
text::Line,
|
|
||||||
widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap},
|
widgets::{Block, BorderType, Borders, Clear, List, ListItem, ListState, Paragraph, Wrap},
|
||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
@ -23,108 +24,21 @@ use crate::tui::{
|
|||||||
lib::interface::musicbrainz::Match,
|
lib::interface::musicbrainz::Match,
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR_BG: Color = Color::Black;
|
pub struct UiColor;
|
||||||
const COLOR_BG_HL: Color = Color::DarkGray;
|
|
||||||
const COLOR_FG: Color = Color::White;
|
impl UiColor {
|
||||||
const COLOR_FG_ERR: Color = Color::Red;
|
const BG: Color = Color::Black;
|
||||||
const COLOR_FG_WARN: Color = Color::LightYellow;
|
const BG_HL: Color = Color::DarkGray;
|
||||||
const COLOR_FG_GOOD: Color = Color::LightGreen;
|
const FG: Color = Color::White;
|
||||||
|
const FG_ERR: Color = Color::Red;
|
||||||
|
const FG_WARN: Color = Color::LightYellow;
|
||||||
|
const FG_GOOD: Color = Color::LightGreen;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait IUi {
|
pub trait IUi {
|
||||||
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
|
fn render<APP: IAppAccess>(app: &mut APP, frame: &mut Frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ArtistArea {
|
|
||||||
list: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AlbumArea {
|
|
||||||
list: Rect,
|
|
||||||
info: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TrackArea {
|
|
||||||
list: Rect,
|
|
||||||
info: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FrameArea {
|
|
||||||
artist: ArtistArea,
|
|
||||||
album: AlbumArea,
|
|
||||||
track: TrackArea,
|
|
||||||
minibuffer: Rect,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrameArea {
|
|
||||||
fn new(frame: Rect) -> Self {
|
|
||||||
let minibuffer_height = 3;
|
|
||||||
let buffer_height = frame.height.saturating_sub(minibuffer_height);
|
|
||||||
|
|
||||||
let width_one_third = frame.width / 3;
|
|
||||||
let height_one_third = buffer_height / 3;
|
|
||||||
|
|
||||||
let panel_width = width_one_third;
|
|
||||||
let panel_width_last = frame.width.saturating_sub(2 * panel_width);
|
|
||||||
let panel_height_top = buffer_height.saturating_sub(height_one_third);
|
|
||||||
let panel_height_bottom = height_one_third;
|
|
||||||
|
|
||||||
let artist_list = Rect {
|
|
||||||
x: frame.x,
|
|
||||||
y: frame.y,
|
|
||||||
width: panel_width,
|
|
||||||
height: buffer_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,
|
|
||||||
};
|
|
||||||
|
|
||||||
let minibuffer = Rect {
|
|
||||||
x: frame.x,
|
|
||||||
y: frame.y + buffer_height,
|
|
||||||
width: frame.width,
|
|
||||||
height: minibuffer_height,
|
|
||||||
};
|
|
||||||
|
|
||||||
FrameArea {
|
|
||||||
artist: ArtistArea { list: artist_list },
|
|
||||||
album: AlbumArea {
|
|
||||||
list: album_list,
|
|
||||||
info: album_info,
|
|
||||||
},
|
|
||||||
track: TrackArea {
|
|
||||||
list: track_list,
|
|
||||||
info: track_info,
|
|
||||||
},
|
|
||||||
minibuffer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum OverlaySize {
|
enum OverlaySize {
|
||||||
MarginFactor(u16),
|
MarginFactor(u16),
|
||||||
Value(u16),
|
Value(u16),
|
||||||
@ -181,29 +95,6 @@ impl OverlayBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ArtistState<'a, 'b> {
|
|
||||||
active: bool,
|
|
||||||
list: List<'a>,
|
|
||||||
state: &'b mut WidgetState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> ArtistState<'a, 'b> {
|
|
||||||
fn new(active: bool, artists: &'a [Artist], state: &'b mut WidgetState) -> ArtistState<'a, 'b> {
|
|
||||||
let list = List::new(
|
|
||||||
artists
|
|
||||||
.iter()
|
|
||||||
.map(|a| ListItem::new(a.id.name.as_str()))
|
|
||||||
.collect::<Vec<ListItem>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
ArtistState {
|
|
||||||
active,
|
|
||||||
list,
|
|
||||||
state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InfoOverlay;
|
struct InfoOverlay;
|
||||||
|
|
||||||
impl InfoOverlay {
|
impl InfoOverlay {
|
||||||
@ -288,142 +179,6 @@ impl<'a> ArtistOverlay<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AlbumState<'a, 'b> {
|
|
||||||
active: bool,
|
|
||||||
list: List<'a>,
|
|
||||||
state: &'b mut WidgetState,
|
|
||||||
info: Paragraph<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> AlbumState<'a, 'b> {
|
|
||||||
fn new(active: bool, albums: &'a [Album], state: &'b mut WidgetState) -> AlbumState<'a, 'b> {
|
|
||||||
let list = List::new(
|
|
||||||
albums
|
|
||||||
.iter()
|
|
||||||
.map(Self::to_list_item)
|
|
||||||
.collect::<Vec<ListItem>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let album = state.list.selected().map(|i| &albums[i]);
|
|
||||||
let info = Paragraph::new(format!(
|
|
||||||
"Title: {}\n\
|
|
||||||
Date: {}\n\
|
|
||||||
Type: {}\n\
|
|
||||||
Status: {}",
|
|
||||||
album.map(|a| a.id.title.as_str()).unwrap_or(""),
|
|
||||||
album
|
|
||||||
.map(|a| Self::display_date(&a.date, &a.seq))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
album
|
|
||||||
.map(|a| Self::display_type(&a.primary_type, &a.secondary_types))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
album
|
|
||||||
.map(|a| Self::display_album_status(&a.get_status()))
|
|
||||||
.unwrap_or("")
|
|
||||||
));
|
|
||||||
|
|
||||||
AlbumState {
|
|
||||||
active,
|
|
||||||
list,
|
|
||||||
state,
|
|
||||||
info,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_list_item(album: &Album) -> ListItem {
|
|
||||||
let line = match album.get_status() {
|
|
||||||
AlbumStatus::None => Line::raw(album.id.title.as_str()),
|
|
||||||
AlbumStatus::Owned(format) => match format {
|
|
||||||
TrackFormat::Mp3 => Line::styled(album.id.title.as_str(), COLOR_FG_WARN),
|
|
||||||
TrackFormat::Flac => Line::styled(album.id.title.as_str(), COLOR_FG_GOOD),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
ListItem::new(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_date(date: &AlbumDate, seq: &AlbumSeq) -> String {
|
|
||||||
if seq.0 > 0 {
|
|
||||||
format!("{} ({})", Self::display_album_date(date), seq.0)
|
|
||||||
} else {
|
|
||||||
Self::display_album_date(date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_album_date(date: &AlbumDate) -> String {
|
|
||||||
match date.year {
|
|
||||||
Some(year) => match date.month {
|
|
||||||
Some(month) => match date.day {
|
|
||||||
Some(day) => format!("{year}‐{month:02}‐{day:02}"),
|
|
||||||
None => format!("{year}‐{month:02}"),
|
|
||||||
},
|
|
||||||
None => format!("{year}"),
|
|
||||||
},
|
|
||||||
None => String::from(""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_type(
|
|
||||||
primary: &Option<AlbumPrimaryType>,
|
|
||||||
secondary: &Vec<AlbumSecondaryType>,
|
|
||||||
) -> String {
|
|
||||||
match primary {
|
|
||||||
Some(ref primary) => {
|
|
||||||
if secondary.is_empty() {
|
|
||||||
Self::display_primary_type(primary).to_string()
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"{} ({})",
|
|
||||||
Self::display_primary_type(primary),
|
|
||||||
Self::display_secondary_types(secondary)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => String::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_primary_type(value: &AlbumPrimaryType) -> &'static str {
|
|
||||||
match value {
|
|
||||||
AlbumPrimaryType::Album => "Album",
|
|
||||||
AlbumPrimaryType::Single => "Single",
|
|
||||||
AlbumPrimaryType::Ep => "EP",
|
|
||||||
AlbumPrimaryType::Broadcast => "Broadcast",
|
|
||||||
AlbumPrimaryType::Other => "Other",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_secondary_types(values: &Vec<AlbumSecondaryType>) -> String {
|
|
||||||
let mut types: Vec<&'static str> = vec![];
|
|
||||||
for value in values {
|
|
||||||
match value {
|
|
||||||
AlbumSecondaryType::Compilation => types.push("Compilation"),
|
|
||||||
AlbumSecondaryType::Soundtrack => types.push("Soundtrack"),
|
|
||||||
AlbumSecondaryType::Spokenword => types.push("Spokenword"),
|
|
||||||
AlbumSecondaryType::Interview => types.push("Interview"),
|
|
||||||
AlbumSecondaryType::Audiobook => types.push("Audiobook"),
|
|
||||||
AlbumSecondaryType::AudioDrama => types.push("Audio drama"),
|
|
||||||
AlbumSecondaryType::Live => types.push("Live"),
|
|
||||||
AlbumSecondaryType::Remix => types.push("Remix"),
|
|
||||||
AlbumSecondaryType::DjMix => types.push("DJ-mix"),
|
|
||||||
AlbumSecondaryType::MixtapeStreet => types.push("Mixtape/Street"),
|
|
||||||
AlbumSecondaryType::Demo => types.push("Demo"),
|
|
||||||
AlbumSecondaryType::FieldRecording => types.push("Field recording"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
types.join(", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_album_status(status: &AlbumStatus) -> &'static str {
|
|
||||||
match status {
|
|
||||||
AlbumStatus::None => "None",
|
|
||||||
AlbumStatus::Owned(format) => match format {
|
|
||||||
TrackFormat::Mp3 => "MP3",
|
|
||||||
TrackFormat::Flac => "FLAC",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AlbumOverlay<'a> {
|
struct AlbumOverlay<'a> {
|
||||||
properties: Paragraph<'a>,
|
properties: Paragraph<'a>,
|
||||||
}
|
}
|
||||||
@ -447,52 +202,6 @@ impl<'a> AlbumOverlay<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrackState<'a, 'b> {
|
|
||||||
active: bool,
|
|
||||||
list: List<'a>,
|
|
||||||
state: &'b mut WidgetState,
|
|
||||||
info: Paragraph<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'b> TrackState<'a, 'b> {
|
|
||||||
fn new(active: bool, tracks: &'a [Track], state: &'b mut WidgetState) -> TrackState<'a, 'b> {
|
|
||||||
let list = List::new(
|
|
||||||
tracks
|
|
||||||
.iter()
|
|
||||||
.map(|tr| ListItem::new(tr.id.title.as_str()))
|
|
||||||
.collect::<Vec<ListItem>>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let track = state.list.selected().map(|i| &tracks[i]);
|
|
||||||
let info = Paragraph::new(format!(
|
|
||||||
"Track: {}\n\
|
|
||||||
Title: {}\n\
|
|
||||||
Artist: {}\n\
|
|
||||||
Quality: {}",
|
|
||||||
track.map(|t| t.number.0.to_string()).unwrap_or_default(),
|
|
||||||
track.map(|t| t.id.title.as_str()).unwrap_or(""),
|
|
||||||
track.map(|t| t.artist.join("; ")).unwrap_or_default(),
|
|
||||||
track
|
|
||||||
.map(|t| Self::display_track_quality(&t.quality))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
TrackState {
|
|
||||||
active,
|
|
||||||
list,
|
|
||||||
state,
|
|
||||||
info,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_track_quality(quality: &TrackQuality) -> String {
|
|
||||||
match quality.format {
|
|
||||||
TrackFormat::Flac => "FLAC".to_string(),
|
|
||||||
TrackFormat::Mp3 => format!("MP3 {}kbps", quality.bitrate),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReloadMenu;
|
struct ReloadMenu;
|
||||||
|
|
||||||
impl ReloadMenu {
|
impl ReloadMenu {
|
||||||
@ -513,11 +222,11 @@ pub struct Ui;
|
|||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
fn style(_active: bool, error: bool) -> Style {
|
fn style(_active: bool, error: bool) -> Style {
|
||||||
let style = Style::default().bg(COLOR_BG);
|
let style = Style::default().bg(UiColor::BG);
|
||||||
if error {
|
if error {
|
||||||
style.fg(COLOR_FG_ERR)
|
style.fg(UiColor::FG_ERR)
|
||||||
} else {
|
} else {
|
||||||
style.fg(COLOR_FG)
|
style.fg(UiColor::FG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -528,9 +237,9 @@ impl Ui {
|
|||||||
fn highlight_style(active: bool) -> Style {
|
fn highlight_style(active: bool) -> Style {
|
||||||
// Do not set the fg color here as it will overwrite any list-specific customisation.
|
// Do not set the fg color here as it will overwrite any list-specific customisation.
|
||||||
if active {
|
if active {
|
||||||
Style::default().bg(COLOR_BG_HL)
|
Style::default().bg(UiColor::BG_HL)
|
||||||
} else {
|
} else {
|
||||||
Style::default().bg(COLOR_BG)
|
Style::default().bg(UiColor::BG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -751,9 +460,9 @@ impl Ui {
|
|||||||
fn display_match_string(match_album: &Match<Album>) -> String {
|
fn display_match_string(match_album: &Match<Album>) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{:010} | {} [{}] ({}%)",
|
"{:010} | {} [{}] ({}%)",
|
||||||
AlbumState::display_album_date(&match_album.item.date),
|
UiDisplay::display_album_date(&match_album.item.date),
|
||||||
&match_album.item.id.title,
|
&match_album.item.id.title,
|
||||||
AlbumState::display_type(
|
UiDisplay::display_type(
|
||||||
&match_album.item.primary_type,
|
&match_album.item.primary_type,
|
||||||
&match_album.item.secondary_types
|
&match_album.item.secondary_types
|
||||||
),
|
),
|
||||||
@ -832,16 +541,105 @@ impl IUi for Ui {
|
|||||||
pub struct UiDisplay;
|
pub struct UiDisplay;
|
||||||
|
|
||||||
impl UiDisplay {
|
impl UiDisplay {
|
||||||
|
fn display_date(date: &AlbumDate, seq: &AlbumSeq) -> String {
|
||||||
|
if seq.0 > 0 {
|
||||||
|
format!("{} ({})", Self::display_album_date(date), seq.0)
|
||||||
|
} else {
|
||||||
|
Self::display_album_date(date)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_album_date(date: &AlbumDate) -> String {
|
||||||
|
match date.year {
|
||||||
|
Some(year) => match date.month {
|
||||||
|
Some(month) => match date.day {
|
||||||
|
Some(day) => format!("{year}‐{month:02}‐{day:02}"),
|
||||||
|
None => format!("{year}‐{month:02}"),
|
||||||
|
},
|
||||||
|
None => format!("{year}"),
|
||||||
|
},
|
||||||
|
None => String::from(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_type(
|
||||||
|
primary: &Option<AlbumPrimaryType>,
|
||||||
|
secondary: &Vec<AlbumSecondaryType>,
|
||||||
|
) -> String {
|
||||||
|
match primary {
|
||||||
|
Some(ref primary) => {
|
||||||
|
if secondary.is_empty() {
|
||||||
|
Self::display_primary_type(primary).to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"{} ({})",
|
||||||
|
Self::display_primary_type(primary),
|
||||||
|
Self::display_secondary_types(secondary)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => String::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_primary_type(value: &AlbumPrimaryType) -> &'static str {
|
||||||
|
match value {
|
||||||
|
AlbumPrimaryType::Album => "Album",
|
||||||
|
AlbumPrimaryType::Single => "Single",
|
||||||
|
AlbumPrimaryType::Ep => "EP",
|
||||||
|
AlbumPrimaryType::Broadcast => "Broadcast",
|
||||||
|
AlbumPrimaryType::Other => "Other",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_secondary_types(values: &Vec<AlbumSecondaryType>) -> String {
|
||||||
|
let mut types: Vec<&'static str> = vec![];
|
||||||
|
for value in values {
|
||||||
|
match value {
|
||||||
|
AlbumSecondaryType::Compilation => types.push("Compilation"),
|
||||||
|
AlbumSecondaryType::Soundtrack => types.push("Soundtrack"),
|
||||||
|
AlbumSecondaryType::Spokenword => types.push("Spokenword"),
|
||||||
|
AlbumSecondaryType::Interview => types.push("Interview"),
|
||||||
|
AlbumSecondaryType::Audiobook => types.push("Audiobook"),
|
||||||
|
AlbumSecondaryType::AudioDrama => types.push("Audio drama"),
|
||||||
|
AlbumSecondaryType::Live => types.push("Live"),
|
||||||
|
AlbumSecondaryType::Remix => types.push("Remix"),
|
||||||
|
AlbumSecondaryType::DjMix => types.push("DJ-mix"),
|
||||||
|
AlbumSecondaryType::MixtapeStreet => types.push("Mixtape/Street"),
|
||||||
|
AlbumSecondaryType::Demo => types.push("Demo"),
|
||||||
|
AlbumSecondaryType::FieldRecording => types.push("Field recording"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
types.join(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_album_status(status: &AlbumStatus) -> &'static str {
|
||||||
|
match status {
|
||||||
|
AlbumStatus::None => "None",
|
||||||
|
AlbumStatus::Owned(format) => match format {
|
||||||
|
TrackFormat::Mp3 => "MP3",
|
||||||
|
TrackFormat::Flac => "FLAC",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn display_matching_info(matching: Option<&Album>) -> String {
|
fn display_matching_info(matching: Option<&Album>) -> String {
|
||||||
match matching {
|
match matching {
|
||||||
Some(matching) => format!(
|
Some(matching) => format!(
|
||||||
"Matching: {} | {}",
|
"Matching: {} | {}",
|
||||||
AlbumState::display_album_date(&matching.date),
|
UiDisplay::display_album_date(&matching.date),
|
||||||
&matching.id.title
|
&matching.id.title
|
||||||
),
|
),
|
||||||
None => String::from("Matching: nothing"),
|
None => String::from("Matching: nothing"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn display_track_quality(quality: &TrackQuality) -> String {
|
||||||
|
match quality.format {
|
||||||
|
TrackFormat::Flac => "FLAC".to_string(),
|
||||||
|
TrackFormat::Mp3 => format!("MP3 {}kbps", quality.bitrate),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -941,121 +739,6 @@ mod tests {
|
|||||||
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
terminal.draw(|frame| Ui::render(&mut app, frame)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_album_date() {
|
|
||||||
assert_eq!(AlbumState::display_album_date(&AlbumDate::default()), "");
|
|
||||||
assert_eq!(AlbumState::display_album_date(&1990.into()), "1990");
|
|
||||||
assert_eq!(AlbumState::display_album_date(&(1990, 5).into()), "1990‐05");
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_album_date(&(1990, 5, 6).into()),
|
|
||||||
"1990‐05‐06"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_date() {
|
|
||||||
let date: AlbumDate = 1990.into();
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_date(&date, &AlbumSeq::default()),
|
|
||||||
"1990"
|
|
||||||
);
|
|
||||||
assert_eq!(AlbumState::display_date(&date, &AlbumSeq(0)), "1990");
|
|
||||||
assert_eq!(AlbumState::display_date(&date, &AlbumSeq(5)), "1990 (5)");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_primary_type() {
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_primary_type(&AlbumPrimaryType::Album),
|
|
||||||
"Album"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_primary_type(&AlbumPrimaryType::Single),
|
|
||||||
"Single"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_primary_type(&AlbumPrimaryType::Ep),
|
|
||||||
"EP"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_primary_type(&AlbumPrimaryType::Broadcast),
|
|
||||||
"Broadcast"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_primary_type(&AlbumPrimaryType::Other),
|
|
||||||
"Other"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_secondary_types() {
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_secondary_types(&vec![
|
|
||||||
AlbumSecondaryType::Compilation,
|
|
||||||
AlbumSecondaryType::Soundtrack,
|
|
||||||
AlbumSecondaryType::Spokenword,
|
|
||||||
AlbumSecondaryType::Interview,
|
|
||||||
AlbumSecondaryType::Audiobook,
|
|
||||||
AlbumSecondaryType::AudioDrama,
|
|
||||||
AlbumSecondaryType::Live,
|
|
||||||
AlbumSecondaryType::Remix,
|
|
||||||
AlbumSecondaryType::DjMix,
|
|
||||||
AlbumSecondaryType::MixtapeStreet,
|
|
||||||
AlbumSecondaryType::Demo,
|
|
||||||
AlbumSecondaryType::FieldRecording,
|
|
||||||
]),
|
|
||||||
"Compilation, Soundtrack, Spokenword, Interview, Audiobook, Audio drama, Live, Remix, \
|
|
||||||
DJ-mix, Mixtape/Street, Demo, Field recording"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_type() {
|
|
||||||
assert_eq!(AlbumState::display_type(&None, &vec![]), "");
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_type(&Some(AlbumPrimaryType::Album), &vec![]),
|
|
||||||
"Album"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_type(
|
|
||||||
&Some(AlbumPrimaryType::Album),
|
|
||||||
&vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation]
|
|
||||||
),
|
|
||||||
"Album (Live, Compilation)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_album_status() {
|
|
||||||
assert_eq!(AlbumState::display_album_status(&AlbumStatus::None), "None");
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Mp3)),
|
|
||||||
"MP3"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
AlbumState::display_album_status(&AlbumStatus::Owned(TrackFormat::Flac)),
|
|
||||||
"FLAC"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn display_track_quality() {
|
|
||||||
assert_eq!(
|
|
||||||
TrackState::display_track_quality(&TrackQuality {
|
|
||||||
format: TrackFormat::Flac,
|
|
||||||
bitrate: 1411
|
|
||||||
}),
|
|
||||||
"FLAC"
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
TrackState::display_track_quality(&TrackQuality {
|
|
||||||
format: TrackFormat::Mp3,
|
|
||||||
bitrate: 218
|
|
||||||
}),
|
|
||||||
"MP3 218kbps"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty() {
|
fn empty() {
|
||||||
let artists: Vec<Artist> = vec![];
|
let artists: Vec<Artist> = vec![];
|
||||||
@ -1096,4 +779,113 @@ mod tests {
|
|||||||
|
|
||||||
draw_test_suite(artists, &mut selection);
|
draw_test_suite(artists, &mut selection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_album_date() {
|
||||||
|
assert_eq!(UiDisplay::display_album_date(&AlbumDate::default()), "");
|
||||||
|
assert_eq!(UiDisplay::display_album_date(&1990.into()), "1990");
|
||||||
|
assert_eq!(UiDisplay::display_album_date(&(1990, 5).into()), "1990‐05");
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_album_date(&(1990, 5, 6).into()),
|
||||||
|
"1990‐05‐06"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_date() {
|
||||||
|
let date: AlbumDate = 1990.into();
|
||||||
|
assert_eq!(UiDisplay::display_date(&date, &AlbumSeq::default()), "1990");
|
||||||
|
assert_eq!(UiDisplay::display_date(&date, &AlbumSeq(0)), "1990");
|
||||||
|
assert_eq!(UiDisplay::display_date(&date, &AlbumSeq(5)), "1990 (5)");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_primary_type() {
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_primary_type(&AlbumPrimaryType::Album),
|
||||||
|
"Album"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_primary_type(&AlbumPrimaryType::Single),
|
||||||
|
"Single"
|
||||||
|
);
|
||||||
|
assert_eq!(UiDisplay::display_primary_type(&AlbumPrimaryType::Ep), "EP");
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_primary_type(&AlbumPrimaryType::Broadcast),
|
||||||
|
"Broadcast"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_primary_type(&AlbumPrimaryType::Other),
|
||||||
|
"Other"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_secondary_types() {
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_secondary_types(&vec![
|
||||||
|
AlbumSecondaryType::Compilation,
|
||||||
|
AlbumSecondaryType::Soundtrack,
|
||||||
|
AlbumSecondaryType::Spokenword,
|
||||||
|
AlbumSecondaryType::Interview,
|
||||||
|
AlbumSecondaryType::Audiobook,
|
||||||
|
AlbumSecondaryType::AudioDrama,
|
||||||
|
AlbumSecondaryType::Live,
|
||||||
|
AlbumSecondaryType::Remix,
|
||||||
|
AlbumSecondaryType::DjMix,
|
||||||
|
AlbumSecondaryType::MixtapeStreet,
|
||||||
|
AlbumSecondaryType::Demo,
|
||||||
|
AlbumSecondaryType::FieldRecording,
|
||||||
|
]),
|
||||||
|
"Compilation, Soundtrack, Spokenword, Interview, Audiobook, Audio drama, Live, Remix, \
|
||||||
|
DJ-mix, Mixtape/Street, Demo, Field recording"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_type() {
|
||||||
|
assert_eq!(UiDisplay::display_type(&None, &vec![]), "");
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_type(&Some(AlbumPrimaryType::Album), &vec![]),
|
||||||
|
"Album"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_type(
|
||||||
|
&Some(AlbumPrimaryType::Album),
|
||||||
|
&vec![AlbumSecondaryType::Live, AlbumSecondaryType::Compilation]
|
||||||
|
),
|
||||||
|
"Album (Live, Compilation)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_album_status() {
|
||||||
|
assert_eq!(UiDisplay::display_album_status(&AlbumStatus::None), "None");
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_album_status(&AlbumStatus::Owned(TrackFormat::Mp3)),
|
||||||
|
"MP3"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_album_status(&AlbumStatus::Owned(TrackFormat::Flac)),
|
||||||
|
"FLAC"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn display_track_quality() {
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_track_quality(&TrackQuality {
|
||||||
|
format: TrackFormat::Flac,
|
||||||
|
bitrate: 1411
|
||||||
|
}),
|
||||||
|
"FLAC"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
UiDisplay::display_track_quality(&TrackQuality {
|
||||||
|
format: TrackFormat::Mp3,
|
||||||
|
bitrate: 218
|
||||||
|
}),
|
||||||
|
"MP3 218kbps"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user