diff --git a/README.md b/README.md
index f8c2c45..da74453 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,7 @@ as the \*BSDs.
- [Custom Keybindings](#custom-keybindings)
- [Proxy](#proxy)
- [Theming](#theming)
+ - [Track Formatting](#track-formatting)
- [Cover Drawing](#cover-drawing)
- [Authentication](#authentication)
@@ -328,6 +329,7 @@ Possible configuration values are:
| `repeat` | Set default repeat mode | `off`, `track`, `playlist` | `off` |
| `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 |
+| `[track_format]` | Set active fields shown in Library/Queue views | See [track formatting](#track-formatting) | |
| `[theme]` | Custom theme | See [custom theme](#theming) | |
| `[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).
+### 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"
+```
+
+Examples: (Click to show/hide)
+
+
+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"
+```
+
+
+
## Cover Drawing
When compiled with the `cover` feature, `ncspot` can draw the album art of the
diff --git a/src/config.rs b/src/config.rs
index 100701f..0d28707 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -33,6 +33,23 @@ pub enum LibraryTab {
Podcasts,
}
+#[derive(Serialize, Deserialize, Debug, Default, Clone)]
+pub struct TrackFormat {
+ pub left: Option,
+ pub center: Option,
+ pub right: Option,
+}
+
+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)]
pub struct ConfigValues {
pub command_key: Option,
@@ -50,12 +67,12 @@ pub struct ConfigValues {
pub volnorm_pregain: Option,
pub notify: Option,
pub bitrate: Option,
- pub album_column: Option,
pub gapless: Option,
pub shuffle: Option,
pub repeat: Option,
pub cover_max_scale: Option,
pub playback_state: Option,
+ pub track_format: Option,
pub library_tabs: Option>,
}
diff --git a/src/model/album.rs b/src/model/album.rs
index 4bf3636..37e5fc2 100644
--- a/src/model/album.rs
+++ b/src/model/album.rs
@@ -165,11 +165,7 @@ impl ListItem for Album {
}
}
- fn as_listitem(&self) -> Box {
- Box::new(self.clone())
- }
-
- fn display_left(&self) -> String {
+ fn display_left(&self, _library: Arc) -> String {
format!("{}", self)
}
@@ -219,14 +215,6 @@ impl ListItem for Album {
}
}
- fn save(&mut self, library: Arc) {
- library.save_album(self);
- }
-
- fn unsave(&mut self, library: Arc) {
- library.unsave_album(self);
- }
-
fn toggle_saved(&mut self, library: Arc) {
if library.is_saved_album(self) {
library.unsave_album(self);
@@ -235,6 +223,14 @@ impl ListItem for Album {
}
}
+ fn save(&mut self, library: Arc) {
+ library.save_album(self);
+ }
+
+ fn unsave(&mut self, library: Arc) {
+ library.unsave_album(self);
+ }
+
fn open(&self, queue: Arc, library: Arc) -> Option> {
Some(AlbumView::new(queue, library, self).into_boxed_view_ext())
}
@@ -301,4 +297,8 @@ impl ListItem for Album {
.collect(),
)
}
+
+ fn as_listitem(&self) -> Box {
+ Box::new(self.clone())
+ }
}
diff --git a/src/model/artist.rs b/src/model/artist.rs
index 4ac4c45..8028e64 100644
--- a/src/model/artist.rs
+++ b/src/model/artist.rs
@@ -94,11 +94,7 @@ impl ListItem for Artist {
}
}
- fn as_listitem(&self) -> Box {
- Box::new(self.clone())
- }
-
- fn display_left(&self) -> String {
+ fn display_left(&self, _library: Arc) -> String {
format!("{}", self)
}
@@ -155,14 +151,6 @@ impl ListItem for Artist {
}
}
- fn save(&mut self, library: Arc) {
- library.follow_artist(self);
- }
-
- fn unsave(&mut self, library: Arc) {
- library.unfollow_artist(self);
- }
-
fn toggle_saved(&mut self, library: Arc) {
if library.is_followed_artist(self) {
library.unfollow_artist(self);
@@ -171,6 +159,14 @@ impl ListItem for Artist {
}
}
+ fn save(&mut self, library: Arc) {
+ library.follow_artist(self);
+ }
+
+ fn unsave(&mut self, library: Arc) {
+ library.unfollow_artist(self);
+ }
+
fn open(&self, queue: Arc, library: Arc) -> Option> {
Some(ArtistView::new(queue, library, self).into_boxed_view_ext())
}
@@ -205,4 +201,8 @@ impl ListItem for Artist {
.clone()
.map(|id| format!("https://open.spotify.com/artist/{}", id))
}
+
+ fn as_listitem(&self) -> Box {
+ Box::new(self.clone())
+ }
}
diff --git a/src/model/episode.rs b/src/model/episode.rs
index 3bdab08..90208a2 100644
--- a/src/model/episode.rs
+++ b/src/model/episode.rs
@@ -75,7 +75,7 @@ impl ListItem for Episode {
.unwrap_or(false)
}
- fn display_left(&self) -> String {
+ fn display_left(&self, _library: Arc) -> String {
self.name.clone()
}
diff --git a/src/model/playable.rs b/src/model/playable.rs
index b887f20..1977cff 100644
--- a/src/model/playable.rs
+++ b/src/model/playable.rs
@@ -19,6 +19,55 @@ pub enum Playable {
}
impl Playable {
+ pub fn format(playable: Playable, formatting: String, library: Arc) -> String {
+ formatting
+ .replace(
+ "%artists",
+ if let Some(artists) = playable.artists() {
+ artists
+ .iter()
+ .map(|artist| artist.clone().name)
+ .collect::>()
+ .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 {
match self {
Playable::Track(track) => track.id.clone(),
@@ -106,8 +155,8 @@ impl ListItem for Playable {
self.as_listitem().is_playing(queue)
}
- fn display_left(&self) -> String {
- self.as_listitem().display_left()
+ fn display_left(&self, library: Arc) -> String {
+ self.as_listitem().display_left(library)
}
fn display_center(&self, library: Arc) -> String {
diff --git a/src/model/playlist.rs b/src/model/playlist.rs
index 4804a66..cdf44a6 100644
--- a/src/model/playlist.rs
+++ b/src/model/playlist.rs
@@ -191,11 +191,7 @@ impl ListItem for Playlist {
}
}
- fn as_listitem(&self) -> Box {
- Box::new(self.clone())
- }
-
- fn display_left(&self) -> String {
+ fn display_left(&self, _library: Arc) -> String {
match self.owner_name.as_ref() {
Some(owner) => format!("{} • {}", self.name, owner),
None => self.name.clone(),
@@ -251,14 +247,6 @@ impl ListItem for Playlist {
}
}
- fn save(&mut self, library: Arc) {
- library.follow_playlist(self);
- }
-
- fn unsave(&mut self, library: Arc) {
- library.delete_playlist(&self.id);
- }
-
fn toggle_saved(&mut self, library: Arc) {
// Don't allow users to unsave their own playlists with one keypress
if !library.is_followed_playlist(self) {
@@ -272,6 +260,14 @@ impl ListItem for Playlist {
}
}
+ fn save(&mut self, library: Arc) {
+ library.follow_playlist(self);
+ }
+
+ fn unsave(&mut self, library: Arc) {
+ library.delete_playlist(&self.id);
+ }
+
fn open(&self, queue: Arc, library: Arc) -> Option> {
Some(PlaylistView::new(queue, library, self).into_boxed_view_ext())
}
@@ -326,4 +322,8 @@ impl ListItem for Playlist {
self.owner_id, self.id
))
}
+
+ fn as_listitem(&self) -> Box {
+ Box::new(self.clone())
+ }
}
diff --git a/src/model/show.rs b/src/model/show.rs
index 94240f5..9dcabd7 100644
--- a/src/model/show.rs
+++ b/src/model/show.rs
@@ -76,7 +76,7 @@ impl ListItem for Show {
false
}
- fn display_left(&self) -> String {
+ fn display_left(&self, _library: Arc) -> String {
format!("{}", self)
}
diff --git a/src/model/track.rs b/src/model/track.rs
index 3e32ff9..13ba762 100644
--- a/src/model/track.rs
+++ b/src/model/track.rs
@@ -1,6 +1,7 @@
use std::fmt;
use std::sync::{Arc, RwLock};
+use crate::config;
use chrono::{DateTime, Utc};
use rspotify::model::album::FullAlbum;
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)
}
- fn as_listitem(&self) -> Box {
- Box::new(self.clone())
- }
-
- fn display_left(&self) -> String {
- format!("{}", self)
+ fn display_left(&self, library: Arc) -> String {
+ let formatting = library
+ .cfg
+ .values()
+ .track_format
+ .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) -> String {
- if library.cfg.values().album_column.unwrap_or(true) {
- self.album.clone().unwrap_or_default()
+ let formatting = library
+ .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 {
- "".to_string()
+ self.album.clone().unwrap_or_default()
}
}
fn display_right(&self, library: Arc) -> String {
- let saved = if library.is_saved_track(&Playable::Track(self.clone())) {
- if library.cfg.values().use_nerdfont.unwrap_or(false) {
- "\u{f62b} "
- } else {
- "✓ "
- }
+ let formatting = library
+ .cfg
+ .values()
+ .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 {
- ""
- };
- format!("{}{}", saved, self.duration_str())
+ let saved = if library.is_saved_track(&Playable::Track(self.clone())) {
+ if library.cfg.values().use_nerdfont.unwrap_or(false) {
+ "\u{f62b}"
+ } else {
+ "✓"
+ }
+ } else {
+ ""
+ };
+ format!("{} {}", saved, self.duration_str())
+ }
}
fn play(&mut self, queue: Arc) {
@@ -223,14 +252,6 @@ impl ListItem for Track {
queue.append(Playable::Track(self.clone()));
}
- fn save(&mut self, library: Arc) {
- library.save_tracks(vec![self], true);
- }
-
- fn unsave(&mut self, library: Arc) {
- library.unsave_tracks(vec![self], true);
- }
-
fn toggle_saved(&mut self, library: Arc) {
if library.is_saved_track(&Playable::Track(self.clone())) {
library.unsave_tracks(vec![self], true);
@@ -239,6 +260,14 @@ impl ListItem for Track {
}
}
+ fn save(&mut self, library: Arc) {
+ library.save_tracks(vec![self], true);
+ }
+
+ fn unsave(&mut self, library: Arc) {
+ library.unsave_tracks(vec![self], true);
+ }
+
fn open(&self, _queue: Arc, _library: Arc) -> Option> {
None
}
@@ -303,4 +332,8 @@ impl ListItem for Track {
fn track(&self) -> Option