Add track_format config option (#800)
* Added track_name_first config option to allow choosing if artists' names should be shown before or after the track name.
* Added active_fields config option, which allows configuration of which columns are visible in Queue/Library view.
This also removes the need for a separate track_name_first and album_column option.
* Fixed README
* Made custom tracklist formatting more flexible.
Updated readme with new instructions.
Reformatted impl member order to match the definitions in traits.rs.
* Added track_name_first config option to allow choosing if artists' names should be shown before or after the track name.
* Added active_fields config option, which allows configuration of which columns are visible in Queue/Library view.
This also removes the need for a separate track_name_first and album_column option.
* Fixed README
* Made custom tracklist formatting more flexible.
Updated readme with new instructions.
Reformatted impl member order to match the definitions in traits.rs.
* Fetch formatting config from library config
Instead of the lazy static mutex
* Moved custom format function to Playable impl as it's a better location to handle both Tracks and Episodes
* Rename from `tracklist_formatting` to `track_format`
Also shorten `format_{left|center|right}` to `{left|center|right}`
Co-authored-by: Henrik Friedrichsen <henrik@affekt.org>
This commit is contained in:
committed by
Henrik Friedrichsen
parent
0e50466a5e
commit
f7450321da
61
README.md
61
README.md
@@ -46,6 +46,7 @@ as the \*BSDs.
|
|||||||
- [Custom Keybindings](#custom-keybindings)
|
- [Custom Keybindings](#custom-keybindings)
|
||||||
- [Proxy](#proxy)
|
- [Proxy](#proxy)
|
||||||
- [Theming](#theming)
|
- [Theming](#theming)
|
||||||
|
- [Track Formatting](#track-formatting)
|
||||||
- [Cover Drawing](#cover-drawing)
|
- [Cover Drawing](#cover-drawing)
|
||||||
- [Authentication](#authentication)
|
- [Authentication](#authentication)
|
||||||
|
|
||||||
@@ -328,6 +329,7 @@ Possible configuration values are:
|
|||||||
| `repeat` | Set default repeat mode | `off`, `track`, `playlist` | `off` |
|
| `repeat` | Set default repeat mode | `off`, `track`, `playlist` | `off` |
|
||||||
| `playback_state` | Set default playback state | `"Stopped"`, `"Paused"`, `"Playing"`, `"Default"` | `"Paused"` |
|
| `playback_state` | Set default playback state | `"Stopped"`, `"Paused"`, `"Playing"`, `"Default"` | `"Paused"` |
|
||||||
| `library_tabs` | Tabs to show in library screen | Array of `tracks`, `albums`, `artists`, `playlists`, `podcasts` | All tabs |
|
| `library_tabs` | Tabs to show in library screen | Array of `tracks`, `albums`, `artists`, `playlists`, `podcasts` | All tabs |
|
||||||
|
| `[track_format]` | Set active fields shown in Library/Queue views | See [track formatting](#track-formatting) | |
|
||||||
| `[theme]` | Custom theme | See [custom theme](#theming) | |
|
| `[theme]` | Custom theme | See [custom theme](#theming) | |
|
||||||
| `[keybindings]` | Custom keybindings | See [custom keybindings](#custom-keybindings) | |
|
| `[keybindings]` | Custom keybindings | See [custom keybindings](#custom-keybindings) | |
|
||||||
|
|
||||||
@@ -385,6 +387,65 @@ search_match = "light red"
|
|||||||
|
|
||||||
More examples can be found in [this pull request](https://github.com/hrkfdn/ncspot/pull/40).
|
More examples can be found in [this pull request](https://github.com/hrkfdn/ncspot/pull/40).
|
||||||
|
|
||||||
|
### Track Formatting
|
||||||
|
It's possible to customize which fields are shown in Queue/Library views.
|
||||||
|
If you don't define `center` for example, the default value will be used.
|
||||||
|
Available options for tracks:
|
||||||
|
`%artists`, `%title`, `%album`, `%saved`, `%duration`
|
||||||
|
Default configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track_format]
|
||||||
|
left = "%artists - %title"
|
||||||
|
center = "%album"
|
||||||
|
right = "%saved %duration"
|
||||||
|
```
|
||||||
|
|
||||||
|
<details><summary>Examples: (Click to show/hide)</summary>
|
||||||
|
|
||||||
|
|
||||||
|
Example 1 - Show only album name and track name after it:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track_format]
|
||||||
|
left = "%album"
|
||||||
|
center = "%title"
|
||||||
|
right = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
Example 2 - Show track title before artists, and don't show album at all:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track_format]
|
||||||
|
left = "%title - %artists"
|
||||||
|
center = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
Example 3 - Show everything as default, but hide saved status and track length:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track_format]
|
||||||
|
right = ""
|
||||||
|
```
|
||||||
|
|
||||||
|
Example 4 - Show everything as default, except show title before artists:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track_format]
|
||||||
|
left = "%title - %artists"
|
||||||
|
```
|
||||||
|
|
||||||
|
Example 5 - Show saved status and duration first, followed by track title and artists, with the album last:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[track_format]
|
||||||
|
left = "|%saved| %duration | %title - %artists"
|
||||||
|
center = ""
|
||||||
|
right = "%album"
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Cover Drawing
|
## Cover Drawing
|
||||||
|
|
||||||
When compiled with the `cover` feature, `ncspot` can draw the album art of the
|
When compiled with the `cover` feature, `ncspot` can draw the album art of the
|
||||||
|
|||||||
@@ -33,6 +33,23 @@ pub enum LibraryTab {
|
|||||||
Podcasts,
|
Podcasts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
|
||||||
|
pub struct TrackFormat {
|
||||||
|
pub left: Option<String>,
|
||||||
|
pub center: Option<String>,
|
||||||
|
pub right: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackFormat {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
TrackFormat {
|
||||||
|
left: Some(String::from("%artists - %title")),
|
||||||
|
center: Some(String::from("%album")),
|
||||||
|
right: Some(String::from("%saved %duration")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct ConfigValues {
|
pub struct ConfigValues {
|
||||||
pub command_key: Option<char>,
|
pub command_key: Option<char>,
|
||||||
@@ -50,12 +67,12 @@ pub struct ConfigValues {
|
|||||||
pub volnorm_pregain: Option<f64>,
|
pub volnorm_pregain: Option<f64>,
|
||||||
pub notify: Option<bool>,
|
pub notify: Option<bool>,
|
||||||
pub bitrate: Option<u32>,
|
pub bitrate: Option<u32>,
|
||||||
pub album_column: Option<bool>,
|
|
||||||
pub gapless: Option<bool>,
|
pub gapless: Option<bool>,
|
||||||
pub shuffle: Option<bool>,
|
pub shuffle: Option<bool>,
|
||||||
pub repeat: Option<queue::RepeatSetting>,
|
pub repeat: Option<queue::RepeatSetting>,
|
||||||
pub cover_max_scale: Option<f32>,
|
pub cover_max_scale: Option<f32>,
|
||||||
pub playback_state: Option<PlaybackState>,
|
pub playback_state: Option<PlaybackState>,
|
||||||
|
pub track_format: Option<TrackFormat>,
|
||||||
pub library_tabs: Option<Vec<LibraryTab>>,
|
pub library_tabs: Option<Vec<LibraryTab>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,11 +165,7 @@ impl ListItem for Album {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_listitem(&self) -> Box<dyn ListItem> {
|
fn display_left(&self, _library: Arc<Library>) -> String {
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_left(&self) -> String {
|
|
||||||
format!("{}", self)
|
format!("{}", self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,14 +215,6 @@ impl ListItem for Album {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, library: Arc<Library>) {
|
|
||||||
library.save_album(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsave(&mut self, library: Arc<Library>) {
|
|
||||||
library.unsave_album(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_saved(&mut self, library: Arc<Library>) {
|
fn toggle_saved(&mut self, library: Arc<Library>) {
|
||||||
if library.is_saved_album(self) {
|
if library.is_saved_album(self) {
|
||||||
library.unsave_album(self);
|
library.unsave_album(self);
|
||||||
@@ -235,6 +223,14 @@ impl ListItem for Album {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save(&mut self, library: Arc<Library>) {
|
||||||
|
library.save_album(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsave(&mut self, library: Arc<Library>) {
|
||||||
|
library.unsave_album(self);
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||||
Some(AlbumView::new(queue, library, self).into_boxed_view_ext())
|
Some(AlbumView::new(queue, library, self).into_boxed_view_ext())
|
||||||
}
|
}
|
||||||
@@ -301,4 +297,8 @@ impl ListItem for Album {
|
|||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_listitem(&self) -> Box<dyn ListItem> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,11 +94,7 @@ impl ListItem for Artist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_listitem(&self) -> Box<dyn ListItem> {
|
fn display_left(&self, _library: Arc<Library>) -> String {
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_left(&self) -> String {
|
|
||||||
format!("{}", self)
|
format!("{}", self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,14 +151,6 @@ impl ListItem for Artist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, library: Arc<Library>) {
|
|
||||||
library.follow_artist(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsave(&mut self, library: Arc<Library>) {
|
|
||||||
library.unfollow_artist(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_saved(&mut self, library: Arc<Library>) {
|
fn toggle_saved(&mut self, library: Arc<Library>) {
|
||||||
if library.is_followed_artist(self) {
|
if library.is_followed_artist(self) {
|
||||||
library.unfollow_artist(self);
|
library.unfollow_artist(self);
|
||||||
@@ -171,6 +159,14 @@ impl ListItem for Artist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save(&mut self, library: Arc<Library>) {
|
||||||
|
library.follow_artist(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsave(&mut self, library: Arc<Library>) {
|
||||||
|
library.unfollow_artist(self);
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||||
Some(ArtistView::new(queue, library, self).into_boxed_view_ext())
|
Some(ArtistView::new(queue, library, self).into_boxed_view_ext())
|
||||||
}
|
}
|
||||||
@@ -205,4 +201,8 @@ impl ListItem for Artist {
|
|||||||
.clone()
|
.clone()
|
||||||
.map(|id| format!("https://open.spotify.com/artist/{}", id))
|
.map(|id| format!("https://open.spotify.com/artist/{}", id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_listitem(&self) -> Box<dyn ListItem> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ impl ListItem for Episode {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_left(&self) -> String {
|
fn display_left(&self, _library: Arc<Library>) -> String {
|
||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,55 @@ pub enum Playable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Playable {
|
impl Playable {
|
||||||
|
pub fn format(playable: Playable, formatting: String, library: Arc<Library>) -> String {
|
||||||
|
formatting
|
||||||
|
.replace(
|
||||||
|
"%artists",
|
||||||
|
if let Some(artists) = playable.artists() {
|
||||||
|
artists
|
||||||
|
.iter()
|
||||||
|
.map(|artist| artist.clone().name)
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ")
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%title",
|
||||||
|
match playable.clone() {
|
||||||
|
Playable::Episode(episode) => episode.name,
|
||||||
|
Playable::Track(track) => track.title,
|
||||||
|
}
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%album",
|
||||||
|
match playable.clone() {
|
||||||
|
Playable::Track(track) => track.album.unwrap_or_default(),
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.replace(
|
||||||
|
"%saved",
|
||||||
|
if library.is_saved_track(&match playable.clone() {
|
||||||
|
Playable::Episode(episode) => Playable::Episode(episode),
|
||||||
|
Playable::Track(track) => Playable::Track(track),
|
||||||
|
}) {
|
||||||
|
if library.cfg.values().use_nerdfont.unwrap_or_default() {
|
||||||
|
"\u{f62b}"
|
||||||
|
} else {
|
||||||
|
"✓"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.replace("%duration", playable.duration_str().as_str())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> Option<String> {
|
pub fn id(&self) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
Playable::Track(track) => track.id.clone(),
|
Playable::Track(track) => track.id.clone(),
|
||||||
@@ -106,8 +155,8 @@ impl ListItem for Playable {
|
|||||||
self.as_listitem().is_playing(queue)
|
self.as_listitem().is_playing(queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_left(&self) -> String {
|
fn display_left(&self, library: Arc<Library>) -> String {
|
||||||
self.as_listitem().display_left()
|
self.as_listitem().display_left(library)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_center(&self, library: Arc<Library>) -> String {
|
fn display_center(&self, library: Arc<Library>) -> String {
|
||||||
|
|||||||
@@ -191,11 +191,7 @@ impl ListItem for Playlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_listitem(&self) -> Box<dyn ListItem> {
|
fn display_left(&self, _library: Arc<Library>) -> String {
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_left(&self) -> String {
|
|
||||||
match self.owner_name.as_ref() {
|
match self.owner_name.as_ref() {
|
||||||
Some(owner) => format!("{} • {}", self.name, owner),
|
Some(owner) => format!("{} • {}", self.name, owner),
|
||||||
None => self.name.clone(),
|
None => self.name.clone(),
|
||||||
@@ -251,14 +247,6 @@ impl ListItem for Playlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, library: Arc<Library>) {
|
|
||||||
library.follow_playlist(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsave(&mut self, library: Arc<Library>) {
|
|
||||||
library.delete_playlist(&self.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_saved(&mut self, library: Arc<Library>) {
|
fn toggle_saved(&mut self, library: Arc<Library>) {
|
||||||
// Don't allow users to unsave their own playlists with one keypress
|
// Don't allow users to unsave their own playlists with one keypress
|
||||||
if !library.is_followed_playlist(self) {
|
if !library.is_followed_playlist(self) {
|
||||||
@@ -272,6 +260,14 @@ impl ListItem for Playlist {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save(&mut self, library: Arc<Library>) {
|
||||||
|
library.follow_playlist(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsave(&mut self, library: Arc<Library>) {
|
||||||
|
library.delete_playlist(&self.id);
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
fn open(&self, queue: Arc<Queue>, library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||||
Some(PlaylistView::new(queue, library, self).into_boxed_view_ext())
|
Some(PlaylistView::new(queue, library, self).into_boxed_view_ext())
|
||||||
}
|
}
|
||||||
@@ -326,4 +322,8 @@ impl ListItem for Playlist {
|
|||||||
self.owner_id, self.id
|
self.owner_id, self.id
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_listitem(&self) -> Box<dyn ListItem> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl ListItem for Show {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_left(&self) -> String {
|
fn display_left(&self, _library: Arc<Library>) -> String {
|
||||||
format!("{}", self)
|
format!("{}", self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use rspotify::model::album::FullAlbum;
|
use rspotify::model::album::FullAlbum;
|
||||||
use rspotify::model::track::{FullTrack, SavedTrack, SimplifiedTrack};
|
use rspotify::model::track::{FullTrack, SavedTrack, SimplifiedTrack};
|
||||||
@@ -181,33 +182,61 @@ impl ListItem for Track {
|
|||||||
current.map(|t| t.id() == self.id).unwrap_or(false)
|
current.map(|t| t.id() == self.id).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_listitem(&self) -> Box<dyn ListItem> {
|
fn display_left(&self, library: Arc<Library>) -> String {
|
||||||
Box::new(self.clone())
|
let formatting = library
|
||||||
}
|
.cfg
|
||||||
|
.values()
|
||||||
fn display_left(&self) -> String {
|
.track_format
|
||||||
format!("{}", self)
|
.clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let default = config::TrackFormat::default().left.unwrap();
|
||||||
|
let left = formatting.left.unwrap_or_else(|| default.clone());
|
||||||
|
if left != default {
|
||||||
|
Playable::format(Playable::Track(self.clone()), left, library)
|
||||||
|
} else {
|
||||||
|
format!("{}", self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_center(&self, library: Arc<Library>) -> String {
|
fn display_center(&self, library: Arc<Library>) -> String {
|
||||||
if library.cfg.values().album_column.unwrap_or(true) {
|
let formatting = library
|
||||||
self.album.clone().unwrap_or_default()
|
.cfg
|
||||||
|
.values()
|
||||||
|
.track_format
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let default = config::TrackFormat::default().center.unwrap();
|
||||||
|
let center = formatting.center.unwrap_or_else(|| default.clone());
|
||||||
|
if center != default {
|
||||||
|
Playable::format(Playable::Track(self.clone()), center, library)
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
self.album.clone().unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_right(&self, library: Arc<Library>) -> String {
|
fn display_right(&self, library: Arc<Library>) -> String {
|
||||||
let saved = if library.is_saved_track(&Playable::Track(self.clone())) {
|
let formatting = library
|
||||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
.cfg
|
||||||
"\u{f62b} "
|
.values()
|
||||||
} else {
|
.track_format
|
||||||
"✓ "
|
.clone()
|
||||||
}
|
.unwrap_or_default();
|
||||||
|
let default = config::TrackFormat::default().right.unwrap();
|
||||||
|
let right = formatting.right.unwrap_or_else(|| default.clone());
|
||||||
|
if right != default {
|
||||||
|
Playable::format(Playable::Track(self.clone()), right, library)
|
||||||
} else {
|
} else {
|
||||||
""
|
let saved = if library.is_saved_track(&Playable::Track(self.clone())) {
|
||||||
};
|
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||||
format!("{}{}", saved, self.duration_str())
|
"\u{f62b}"
|
||||||
|
} else {
|
||||||
|
"✓"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
format!("{} {}", saved, self.duration_str())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play(&mut self, queue: Arc<Queue>) {
|
fn play(&mut self, queue: Arc<Queue>) {
|
||||||
@@ -223,14 +252,6 @@ impl ListItem for Track {
|
|||||||
queue.append(Playable::Track(self.clone()));
|
queue.append(Playable::Track(self.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, library: Arc<Library>) {
|
|
||||||
library.save_tracks(vec![self], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unsave(&mut self, library: Arc<Library>) {
|
|
||||||
library.unsave_tracks(vec![self], true);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_saved(&mut self, library: Arc<Library>) {
|
fn toggle_saved(&mut self, library: Arc<Library>) {
|
||||||
if library.is_saved_track(&Playable::Track(self.clone())) {
|
if library.is_saved_track(&Playable::Track(self.clone())) {
|
||||||
library.unsave_tracks(vec![self], true);
|
library.unsave_tracks(vec![self], true);
|
||||||
@@ -239,6 +260,14 @@ impl ListItem for Track {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn save(&mut self, library: Arc<Library>) {
|
||||||
|
library.save_tracks(vec![self], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsave(&mut self, library: Arc<Library>) {
|
||||||
|
library.unsave_tracks(vec![self], true);
|
||||||
|
}
|
||||||
|
|
||||||
fn open(&self, _queue: Arc<Queue>, _library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
fn open(&self, _queue: Arc<Queue>, _library: Arc<Library>) -> Option<Box<dyn ViewExt>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -303,4 +332,8 @@ impl ListItem for Track {
|
|||||||
fn track(&self) -> Option<Track> {
|
fn track(&self) -> Option<Track> {
|
||||||
Some(self.clone())
|
Some(self.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_listitem(&self) -> Box<dyn ListItem> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ use crate::queue::Queue;
|
|||||||
|
|
||||||
pub trait ListItem: Sync + Send + 'static {
|
pub trait ListItem: Sync + Send + 'static {
|
||||||
fn is_playing(&self, queue: Arc<Queue>) -> bool;
|
fn is_playing(&self, queue: Arc<Queue>) -> bool;
|
||||||
fn display_left(&self) -> String;
|
fn display_left(&self, library: Arc<Library>) -> String;
|
||||||
fn display_center(&self, _library: Arc<Library>) -> String {
|
fn display_center(&self, _library: Arc<Library>) -> String {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
fn display_right(&self, library: Arc<Library>) -> String;
|
fn display_right(&self, library: Arc<Library>) -> String;
|
||||||
fn play(&mut self, queue: Arc<Queue>);
|
fn play(&mut self, queue: Arc<Queue>);
|
||||||
fn queue(&mut self, queue: Arc<Queue>);
|
|
||||||
fn play_next(&mut self, queue: Arc<Queue>);
|
fn play_next(&mut self, queue: Arc<Queue>);
|
||||||
|
fn queue(&mut self, queue: Arc<Queue>);
|
||||||
fn toggle_saved(&mut self, library: Arc<Library>);
|
fn toggle_saved(&mut self, library: Arc<Library>);
|
||||||
fn save(&mut self, library: Arc<Library>);
|
fn save(&mut self, library: Arc<Library>);
|
||||||
fn unsave(&mut self, library: Arc<Library>);
|
fn unsave(&mut self, library: Arc<Library>);
|
||||||
|
|||||||
@@ -215,48 +215,51 @@ impl ContextMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open detail view of artist/album
|
// open detail view of artist/album
|
||||||
content.set_on_submit(move |s: &mut Cursive, action: &ContextMenuAction| {
|
{
|
||||||
s.pop_layer();
|
|
||||||
let queue = queue.clone();
|
|
||||||
let library = library.clone();
|
let library = library.clone();
|
||||||
|
content.set_on_submit(move |s: &mut Cursive, action: &ContextMenuAction| {
|
||||||
|
let queue = queue.clone();
|
||||||
|
let library = library.clone();
|
||||||
|
s.pop_layer();
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
ContextMenuAction::PlayTrack(track) => {
|
ContextMenuAction::PlayTrack(track) => {
|
||||||
let dialog = Self::play_track_dialog(queue, *track.clone());
|
let dialog = Self::play_track_dialog(queue, *track.clone());
|
||||||
s.add_layer(dialog);
|
s.add_layer(dialog);
|
||||||
}
|
}
|
||||||
ContextMenuAction::ShowItem(item) => {
|
ContextMenuAction::ShowItem(item) => {
|
||||||
if let Some(view) = item.open(queue, library) {
|
if let Some(view) = item.open(queue, library) {
|
||||||
s.call_on_name("main", move |v: &mut Layout| v.push_view(view));
|
s.call_on_name("main", move |v: &mut Layout| v.push_view(view));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextMenuAction::ShareUrl(url) => {
|
||||||
|
#[cfg(feature = "share_clipboard")]
|
||||||
|
write_share(url.to_string());
|
||||||
|
}
|
||||||
|
ContextMenuAction::AddToPlaylist(track) => {
|
||||||
|
let dialog =
|
||||||
|
Self::add_track_dialog(library, queue.get_spotify(), *track.clone());
|
||||||
|
s.add_layer(dialog);
|
||||||
|
}
|
||||||
|
ContextMenuAction::ShowRecommendations(item) => {
|
||||||
|
if let Some(view) = item.to_owned().open_recommendations(queue, library) {
|
||||||
|
s.call_on_name("main", move |v: &mut Layout| v.push_view(view));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ContextMenuAction::ToggleTrackSavedStatus(track) => {
|
||||||
|
let mut track: Track = *track.clone();
|
||||||
|
track.toggle_saved(library);
|
||||||
|
}
|
||||||
|
ContextMenuAction::SelectArtist(artists) => {
|
||||||
|
let dialog = Self::select_artist_dialog(library, queue, artists.clone());
|
||||||
|
s.add_layer(dialog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContextMenuAction::ShareUrl(url) => {
|
});
|
||||||
#[cfg(feature = "share_clipboard")]
|
}
|
||||||
write_share(url.to_string());
|
|
||||||
}
|
|
||||||
ContextMenuAction::AddToPlaylist(track) => {
|
|
||||||
let dialog =
|
|
||||||
Self::add_track_dialog(library, queue.get_spotify(), *track.clone());
|
|
||||||
s.add_layer(dialog);
|
|
||||||
}
|
|
||||||
ContextMenuAction::ShowRecommendations(item) => {
|
|
||||||
if let Some(view) = item.to_owned().open_recommendations(queue, library) {
|
|
||||||
s.call_on_name("main", move |v: &mut Layout| v.push_view(view));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ContextMenuAction::ToggleTrackSavedStatus(track) => {
|
|
||||||
let mut track: Track = *track.clone();
|
|
||||||
track.toggle_saved(library);
|
|
||||||
}
|
|
||||||
ContextMenuAction::SelectArtist(artists) => {
|
|
||||||
let dialog = Self::select_artist_dialog(library, queue, artists.clone());
|
|
||||||
s.add_layer(dialog);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let dialog = Dialog::new()
|
let dialog = Dialog::new()
|
||||||
.title(item.display_left())
|
.title(item.display_left(library))
|
||||||
.dismiss_button("Cancel")
|
.dismiss_button("Cancel")
|
||||||
.padding(Margins::lrtb(1, 1, 1, 0))
|
.padding(Margins::lrtb(1, 1, 1, 0))
|
||||||
.content(content.with_name("contextmenu_select"));
|
.content(content.with_name("contextmenu_select"));
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ impl<I: ListItem> ListView<I> {
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(_, i)| {
|
.filter(|(_, i)| {
|
||||||
i.display_left()
|
i.display_left(self.library.clone())
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
.contains(&query[..].to_lowercase())
|
.contains(&query[..].to_lowercase())
|
||||||
})
|
})
|
||||||
@@ -189,7 +189,7 @@ impl<I: ListItem> View for ListView<I> {
|
|||||||
ColorStyle::primary()
|
ColorStyle::primary()
|
||||||
};
|
};
|
||||||
|
|
||||||
let left = item.display_left();
|
let left = item.display_left(self.library.clone());
|
||||||
let center = item.display_center(self.library.clone());
|
let center = item.display_center(self.library.clone());
|
||||||
let right = item.display_right(self.library.clone());
|
let right = item.display_right(self.library.clone());
|
||||||
let draw_center = !center.is_empty();
|
let draw_center = !center.is_empty();
|
||||||
|
|||||||
Reference in New Issue
Block a user