refactor: pass globally mutable config reference
Before, copies of the configuration were passed over. This change also causes configuration reloads to affect the entire application and is easier to maintain but introduces some RwLock overhead.
This commit is contained in:
@@ -182,7 +182,7 @@ impl ListItem for Album {
|
||||
|
||||
fn display_right(&self, library: Arc<Library>) -> String {
|
||||
let saved = if library.is_saved_album(self) {
|
||||
if library.use_nerdfont {
|
||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
|
||||
@@ -154,7 +154,7 @@ impl ListItem for Artist {
|
||||
|
||||
fn display_right(&self, library: Arc<Library>) -> String {
|
||||
let followed = if library.is_followed_artist(self) {
|
||||
if library.use_nerdfont {
|
||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
|
||||
@@ -32,6 +32,7 @@ pub struct CommandManager {
|
||||
spotify: Arc<Spotify>,
|
||||
queue: Arc<Queue>,
|
||||
library: Arc<Library>,
|
||||
config: Arc<Config>,
|
||||
}
|
||||
|
||||
impl CommandManager {
|
||||
@@ -39,18 +40,21 @@ impl CommandManager {
|
||||
spotify: Arc<Spotify>,
|
||||
queue: Arc<Queue>,
|
||||
library: Arc<Library>,
|
||||
config: &Config,
|
||||
config: Arc<Config>,
|
||||
) -> CommandManager {
|
||||
let bindings = RefCell::new(Self::get_bindings(config.clone()));
|
||||
CommandManager {
|
||||
aliases: HashMap::new(),
|
||||
bindings: RefCell::new(Self::get_bindings(config)),
|
||||
bindings,
|
||||
spotify,
|
||||
queue,
|
||||
library,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_bindings(config: &Config) -> HashMap<String, Command> {
|
||||
pub fn get_bindings(config: Arc<Config>) -> HashMap<String, Command> {
|
||||
let config = config.values();
|
||||
let mut kb = if config.default_keybindings.unwrap_or(true) {
|
||||
Self::default_keybindings()
|
||||
} else {
|
||||
@@ -161,15 +165,16 @@ impl CommandManager {
|
||||
Ok(None)
|
||||
}
|
||||
Command::ReloadConfig => {
|
||||
let cfg = crate::config::load()?;
|
||||
self.config.reload();
|
||||
|
||||
// update theme
|
||||
let theme = crate::theme::load(&cfg);
|
||||
let theme = self.config.build_theme();
|
||||
s.set_theme(theme);
|
||||
|
||||
// update bindings
|
||||
self.unregister_keybindings(s);
|
||||
self.bindings.replace(Self::get_bindings(&cfg));
|
||||
self.bindings
|
||||
.replace(Self::get_bindings(self.config.clone()));
|
||||
self.register_keybindings(s);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::RwLock;
|
||||
use std::sync::{RwLock, RwLockReadGuard};
|
||||
use std::{fs, process};
|
||||
|
||||
use cursive::theme::Theme;
|
||||
use platform_dirs::AppDirs;
|
||||
|
||||
pub const CLIENT_ID: &str = "d420a117a32841c2b3474932e49fb54b";
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
|
||||
pub struct Config {
|
||||
pub struct ConfigValues {
|
||||
pub default_keybindings: Option<bool>,
|
||||
pub keybindings: Option<HashMap<String, String>>,
|
||||
pub theme: Option<ConfigTheme>,
|
||||
@@ -57,9 +58,40 @@ lazy_static! {
|
||||
pub static ref BASE_PATH: RwLock<Option<PathBuf>> = RwLock::new(None);
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Config, String> {
|
||||
pub struct Config {
|
||||
values: RwLock<ConfigValues>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new() -> Self {
|
||||
let values = load().unwrap_or_else(|e| {
|
||||
eprintln!("could not load config: {}", e);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
Self {
|
||||
values: RwLock::new(values),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values(&self) -> RwLockReadGuard<ConfigValues> {
|
||||
self.values.read().expect("can't readlock config values")
|
||||
}
|
||||
|
||||
pub fn build_theme(&self) -> Theme {
|
||||
let theme = &self.values().theme;
|
||||
crate::theme::load(theme)
|
||||
}
|
||||
|
||||
pub fn reload(&self) {
|
||||
let cfg = load().expect("could not reload config");
|
||||
*self.values.write().expect("can't writelock config values") = cfg
|
||||
}
|
||||
}
|
||||
|
||||
fn load() -> Result<ConfigValues, String> {
|
||||
let path = config_path("config.toml");
|
||||
load_or_generate_default(path, |_| Ok(Config::default()), false)
|
||||
load_or_generate_default(path, |_| Ok(ConfigValues::default()), false)
|
||||
}
|
||||
|
||||
fn proj_dirs() -> AppDirs {
|
||||
|
||||
@@ -36,11 +36,11 @@ pub struct Library {
|
||||
user_id: Option<String>,
|
||||
ev: EventManager,
|
||||
spotify: Arc<Spotify>,
|
||||
pub use_nerdfont: bool,
|
||||
pub cfg: Arc<Config>,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
pub fn new(ev: &EventManager, spotify: Arc<Spotify>, use_nerdfont: bool) -> Self {
|
||||
pub fn new(ev: &EventManager, spotify: Arc<Spotify>, cfg: Arc<Config>) -> Self {
|
||||
let user_id = spotify.current_user().map(|u| u.id);
|
||||
|
||||
let library = Self {
|
||||
@@ -53,7 +53,7 @@ impl Library {
|
||||
user_id,
|
||||
ev: ev.clone(),
|
||||
spotify,
|
||||
use_nerdfont,
|
||||
cfg,
|
||||
};
|
||||
|
||||
library.update_library();
|
||||
@@ -66,10 +66,6 @@ impl Library {
|
||||
.expect("could not readlock listview content")
|
||||
}
|
||||
|
||||
pub fn config(&self) -> &Config {
|
||||
&self.spotify.cfg
|
||||
}
|
||||
|
||||
fn load_cache<T: DeserializeOwned>(&self, cache_path: PathBuf, store: Arc<RwLock<Vec<T>>>) {
|
||||
if let Ok(contents) = std::fs::read_to_string(&cache_path) {
|
||||
debug!("loading cache from {}", cache_path.display());
|
||||
|
||||
28
src/main.rs
28
src/main.rs
@@ -77,6 +77,7 @@ mod mpris;
|
||||
|
||||
use crate::command::{Command, JumpMode};
|
||||
use crate::commands::CommandManager;
|
||||
use crate::config::Config;
|
||||
use crate::events::{Event, EventManager};
|
||||
use crate::library::Library;
|
||||
use crate::spotify::PlayerEvent;
|
||||
@@ -189,10 +190,7 @@ fn main() {
|
||||
|
||||
// Things here may cause the process to abort; we must do them before creating curses windows
|
||||
// otherwise the error message will not be seen by a user
|
||||
let cfg: crate::config::Config = config::load().unwrap_or_else(|e| {
|
||||
eprintln!("{}", e);
|
||||
process::exit(1);
|
||||
});
|
||||
let cfg: Arc<crate::config::Config> = Arc::new(Config::new());
|
||||
|
||||
let cache = Cache::new(config::cache_path("librespot"), true);
|
||||
let mut credentials = {
|
||||
@@ -218,9 +216,8 @@ fn main() {
|
||||
credentials = credentials_prompt(reset, Some(error_msg));
|
||||
}
|
||||
|
||||
let theme = theme::load(&cfg);
|
||||
|
||||
let mut cursive = Cursive::default();
|
||||
let theme = cfg.build_theme();
|
||||
cursive.set_theme(theme.clone());
|
||||
|
||||
let event_manager = EventManager::new(cursive.cb_sink().clone());
|
||||
@@ -228,22 +225,18 @@ fn main() {
|
||||
let spotify = Arc::new(spotify::Spotify::new(
|
||||
event_manager.clone(),
|
||||
credentials,
|
||||
&cfg,
|
||||
cfg.clone(),
|
||||
));
|
||||
|
||||
let queue = Arc::new(queue::Queue::new(spotify.clone()));
|
||||
let queue = Arc::new(queue::Queue::new(spotify.clone(), cfg.clone()));
|
||||
|
||||
#[cfg(feature = "mpris")]
|
||||
let mpris_manager = Arc::new(mpris::MprisManager::new(spotify.clone(), queue.clone()));
|
||||
|
||||
let library = Arc::new(Library::new(
|
||||
&event_manager,
|
||||
spotify.clone(),
|
||||
cfg.use_nerdfont.unwrap_or(false),
|
||||
));
|
||||
let library = Arc::new(Library::new(&event_manager, spotify.clone(), cfg.clone()));
|
||||
|
||||
let mut cmd_manager =
|
||||
CommandManager::new(spotify.clone(), queue.clone(), library.clone(), &cfg);
|
||||
CommandManager::new(spotify.clone(), queue.clone(), library.clone(), cfg.clone());
|
||||
|
||||
cmd_manager.register_all();
|
||||
cmd_manager.register_keybindings(&mut cursive);
|
||||
@@ -262,8 +255,11 @@ fn main() {
|
||||
|
||||
let queueview = ui::queue::QueueView::new(queue.clone(), library.clone());
|
||||
|
||||
let status =
|
||||
ui::statusbar::StatusBar::new(queue.clone(), library, cfg.use_nerdfont.unwrap_or(false));
|
||||
let status = ui::statusbar::StatusBar::new(
|
||||
queue.clone(),
|
||||
library,
|
||||
cfg.values().use_nerdfont.unwrap_or(false),
|
||||
);
|
||||
|
||||
let mut layout = ui::layout::Layout::new(status, &event_manager, theme)
|
||||
.view("search", search.with_name("search"), "Search")
|
||||
|
||||
@@ -134,7 +134,7 @@ impl ListItem for Playlist {
|
||||
|
||||
fn display_right(&self, library: Arc<Library>) -> String {
|
||||
let saved = if library.is_saved_playlist(self) {
|
||||
if library.use_nerdfont {
|
||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
|
||||
@@ -7,6 +7,7 @@ use notify_rust::Notification;
|
||||
use rand::prelude::*;
|
||||
use strum_macros::Display;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::playable::Playable;
|
||||
use crate::spotify::Spotify;
|
||||
|
||||
@@ -23,16 +24,18 @@ pub struct Queue {
|
||||
current_track: RwLock<Option<usize>>,
|
||||
repeat: RwLock<RepeatSetting>,
|
||||
spotify: Arc<Spotify>,
|
||||
cfg: Arc<Config>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn new(spotify: Arc<Spotify>) -> Queue {
|
||||
pub fn new(spotify: Arc<Spotify>, cfg: Arc<Config>) -> Queue {
|
||||
let q = Queue {
|
||||
queue: Arc::new(RwLock::new(Vec::new())),
|
||||
spotify,
|
||||
current_track: RwLock::new(None),
|
||||
repeat: RwLock::new(RepeatSetting::None),
|
||||
random_order: RwLock::new(None),
|
||||
cfg,
|
||||
};
|
||||
q.set_repeat(q.spotify.repeat);
|
||||
q.set_shuffle(q.spotify.shuffle);
|
||||
@@ -248,7 +251,7 @@ impl Queue {
|
||||
let mut current = self.current_track.write().unwrap();
|
||||
current.replace(index);
|
||||
self.spotify.update_track();
|
||||
if self.spotify.cfg.notify.unwrap_or(false) {
|
||||
if self.cfg.values().notify.unwrap_or(false) {
|
||||
#[cfg(feature = "notify")]
|
||||
if let Err(e) = Notification::new().summary(&track.to_string()).show() {
|
||||
error!("error showing notification: {:?}", e);
|
||||
|
||||
@@ -98,7 +98,7 @@ impl ListItem for Show {
|
||||
|
||||
fn display_right(&self, library: Arc<Library>) -> String {
|
||||
let saved = if library.is_saved_show(self) {
|
||||
if library.use_nerdfont {
|
||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
|
||||
@@ -48,7 +48,7 @@ use core::task::Poll;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::RwLock;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::{env, io};
|
||||
@@ -84,7 +84,7 @@ pub enum PlayerEvent {
|
||||
pub struct Spotify {
|
||||
events: EventManager,
|
||||
credentials: Credentials,
|
||||
pub cfg: config::Config,
|
||||
cfg: Arc<config::Config>,
|
||||
status: RwLock<PlayerEvent>,
|
||||
api: RwLock<SpotifyAPI>,
|
||||
elapsed: RwLock<Option<Duration>>,
|
||||
@@ -246,15 +246,19 @@ impl futures::Future for Worker {
|
||||
}
|
||||
|
||||
impl Spotify {
|
||||
pub fn new(events: EventManager, credentials: Credentials, cfg: &config::Config) -> Spotify {
|
||||
let volume = match &cfg.saved_state {
|
||||
pub fn new(
|
||||
events: EventManager,
|
||||
credentials: Credentials,
|
||||
cfg: Arc<config::Config>,
|
||||
) -> Spotify {
|
||||
let volume = match &cfg.values().saved_state {
|
||||
Some(state) => match state.volume {
|
||||
Some(vol) => ((std::cmp::min(vol, 100) as f32) / 100.0 * 65535_f32).ceil() as u16,
|
||||
None => 0xFFFF as u16,
|
||||
},
|
||||
None => 0xFFFF as u16,
|
||||
};
|
||||
let repeat = match &cfg.saved_state {
|
||||
let repeat = match &cfg.values().saved_state {
|
||||
Some(state) => match &state.repeat {
|
||||
Some(s) => match s.as_str() {
|
||||
"track" => queue::RepeatSetting::RepeatTrack,
|
||||
@@ -265,7 +269,7 @@ impl Spotify {
|
||||
},
|
||||
_ => queue::RepeatSetting::None,
|
||||
};
|
||||
let shuffle = match &cfg.saved_state {
|
||||
let shuffle = match &cfg.values().saved_state {
|
||||
Some(state) => matches!(&state.shuffle, Some(true)),
|
||||
None => false,
|
||||
};
|
||||
@@ -273,7 +277,7 @@ impl Spotify {
|
||||
let mut spotify = Spotify {
|
||||
events,
|
||||
credentials,
|
||||
cfg: cfg.clone(),
|
||||
cfg,
|
||||
status: RwLock::new(PlayerEvent::Stopped),
|
||||
api: RwLock::new(SpotifyAPI::default()),
|
||||
elapsed: RwLock::new(None),
|
||||
@@ -306,7 +310,14 @@ impl Spotify {
|
||||
let volume = self.volume();
|
||||
let credentials = self.credentials.clone();
|
||||
thread::spawn(move || {
|
||||
Self::worker(events, Box::pin(rx), cfg, credentials, user_tx, volume)
|
||||
Self::worker(
|
||||
events,
|
||||
Box::pin(rx),
|
||||
cfg.clone(),
|
||||
credentials,
|
||||
user_tx,
|
||||
volume,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -349,7 +360,7 @@ impl Spotify {
|
||||
let session_config = Self::session_config();
|
||||
let cache = Cache::new(
|
||||
config::cache_path("librespot"),
|
||||
cfg.audio_cache.unwrap_or(true),
|
||||
cfg.values().audio_cache.unwrap_or(true),
|
||||
);
|
||||
let handle = core.handle();
|
||||
debug!("opening spotify session");
|
||||
@@ -391,12 +402,12 @@ impl Spotify {
|
||||
fn worker(
|
||||
events: EventManager,
|
||||
commands: Pin<Box<mpsc::UnboundedReceiver<WorkerCommand>>>,
|
||||
cfg: config::Config,
|
||||
cfg: Arc<config::Config>,
|
||||
credentials: Credentials,
|
||||
user_tx: Option<oneshot::Sender<String>>,
|
||||
volume: u16,
|
||||
) {
|
||||
let bitrate_str = cfg.bitrate.unwrap_or(320).to_string();
|
||||
let bitrate_str = cfg.values().bitrate.unwrap_or(320).to_string();
|
||||
let bitrate = Bitrate::from_str(&bitrate_str);
|
||||
if bitrate.is_err() {
|
||||
error!("invalid bitrate, will use 320 instead")
|
||||
@@ -405,8 +416,8 @@ impl Spotify {
|
||||
let player_config = PlayerConfig {
|
||||
gapless: false,
|
||||
bitrate: bitrate.unwrap_or(Bitrate::Bitrate320),
|
||||
normalisation: cfg.volnorm.unwrap_or(false),
|
||||
normalisation_pregain: cfg.volnorm_pregain.unwrap_or(0.0),
|
||||
normalisation: cfg.values().volnorm.unwrap_or(false),
|
||||
normalisation_pregain: cfg.values().volnorm_pregain.unwrap_or(0.0),
|
||||
};
|
||||
|
||||
let mut core = Core::new().unwrap();
|
||||
@@ -419,12 +430,12 @@ impl Spotify {
|
||||
let mixer = create_mixer(None);
|
||||
mixer.set_volume(volume);
|
||||
|
||||
let backend = audio_backend::find(cfg.backend.clone()).unwrap();
|
||||
let backend = audio_backend::find(cfg.values().backend.clone()).unwrap();
|
||||
let (player, player_events) = Player::new(
|
||||
player_config,
|
||||
session.clone(),
|
||||
mixer.get_audio_filter(),
|
||||
move || (backend)(cfg.backend_device),
|
||||
move || (backend)(cfg.values().backend_device.clone()),
|
||||
);
|
||||
|
||||
let worker = Worker::new(
|
||||
|
||||
55
src/theme.rs
55
src/theme.rs
@@ -3,11 +3,11 @@ use cursive::theme::Color::*;
|
||||
use cursive::theme::PaletteColor::*;
|
||||
use cursive::theme::*;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::ConfigTheme;
|
||||
|
||||
macro_rules! load_color {
|
||||
( $cfg: expr, $member: ident, $default: expr ) => {
|
||||
$cfg.theme
|
||||
( $theme: expr, $member: ident, $default: expr ) => {
|
||||
$theme
|
||||
.as_ref()
|
||||
.and_then(|t| t.$member.clone())
|
||||
.map(|c| Color::parse(c.as_ref()).expect(&format!("Failed to parse color \"{}\"", c)))
|
||||
@@ -15,41 +15,50 @@ macro_rules! load_color {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load(cfg: &Config) -> Theme {
|
||||
pub fn load(theme_cfg: &Option<ConfigTheme>) -> Theme {
|
||||
let mut palette = Palette::default();
|
||||
let borders = BorderStyle::Simple;
|
||||
|
||||
palette[Background] = load_color!(cfg, background, TerminalDefault);
|
||||
palette[View] = load_color!(cfg, background, TerminalDefault);
|
||||
palette[Primary] = load_color!(cfg, primary, TerminalDefault);
|
||||
palette[Secondary] = load_color!(cfg, secondary, Dark(Blue));
|
||||
palette[TitlePrimary] = load_color!(cfg, title, Dark(Red));
|
||||
palette[Tertiary] = load_color!(cfg, highlight, TerminalDefault);
|
||||
palette[Highlight] = load_color!(cfg, highlight_bg, Dark(Red));
|
||||
palette.set_color("playing", load_color!(cfg, playing, Dark(Blue)));
|
||||
palette[Background] = load_color!(theme_cfg, background, TerminalDefault);
|
||||
palette[View] = load_color!(theme_cfg, background, TerminalDefault);
|
||||
palette[Primary] = load_color!(theme_cfg, primary, TerminalDefault);
|
||||
palette[Secondary] = load_color!(theme_cfg, secondary, Dark(Blue));
|
||||
palette[TitlePrimary] = load_color!(theme_cfg, title, Dark(Red));
|
||||
palette[Tertiary] = load_color!(theme_cfg, highlight, TerminalDefault);
|
||||
palette[Highlight] = load_color!(theme_cfg, highlight_bg, Dark(Red));
|
||||
palette.set_color("playing", load_color!(theme_cfg, playing, Dark(Blue)));
|
||||
palette.set_color(
|
||||
"playing_selected",
|
||||
load_color!(cfg, playing_selected, Light(Blue)),
|
||||
load_color!(theme_cfg, playing_selected, Light(Blue)),
|
||||
);
|
||||
palette.set_color("playing_bg", load_color!(cfg, playing_bg, TerminalDefault));
|
||||
palette.set_color("error", load_color!(cfg, error, TerminalDefault));
|
||||
palette.set_color("error_bg", load_color!(cfg, error_bg, Dark(Red)));
|
||||
palette.set_color(
|
||||
"playing_bg",
|
||||
load_color!(theme_cfg, playing_bg, TerminalDefault),
|
||||
);
|
||||
palette.set_color("error", load_color!(theme_cfg, error, TerminalDefault));
|
||||
palette.set_color("error_bg", load_color!(theme_cfg, error_bg, Dark(Red)));
|
||||
palette.set_color(
|
||||
"statusbar_progress",
|
||||
load_color!(cfg, statusbar_progress, Dark(Blue)),
|
||||
load_color!(theme_cfg, statusbar_progress, Dark(Blue)),
|
||||
);
|
||||
palette.set_color(
|
||||
"statusbar_progress_bg",
|
||||
load_color!(cfg, statusbar_progress_bg, Light(Black)),
|
||||
load_color!(theme_cfg, statusbar_progress_bg, Light(Black)),
|
||||
);
|
||||
palette.set_color("statusbar", load_color!(cfg, statusbar, Dark(Yellow)));
|
||||
palette.set_color("statusbar", load_color!(theme_cfg, statusbar, Dark(Yellow)));
|
||||
palette.set_color(
|
||||
"statusbar_bg",
|
||||
load_color!(cfg, statusbar_bg, TerminalDefault),
|
||||
load_color!(theme_cfg, statusbar_bg, TerminalDefault),
|
||||
);
|
||||
palette.set_color("cmdline", load_color!(theme_cfg, cmdline, TerminalDefault));
|
||||
palette.set_color(
|
||||
"cmdline_bg",
|
||||
load_color!(theme_cfg, cmdline_bg, TerminalDefault),
|
||||
);
|
||||
palette.set_color(
|
||||
"search_match",
|
||||
load_color!(theme_cfg, search_match, Light(Red)),
|
||||
);
|
||||
palette.set_color("cmdline", load_color!(cfg, cmdline, TerminalDefault));
|
||||
palette.set_color("cmdline_bg", load_color!(cfg, cmdline_bg, TerminalDefault));
|
||||
palette.set_color("search_match", load_color!(cfg, search_match, Light(Red)));
|
||||
|
||||
Theme {
|
||||
shadow: false,
|
||||
|
||||
@@ -154,7 +154,7 @@ impl ListItem for Track {
|
||||
}
|
||||
|
||||
fn display_center(&self, library: Arc<Library>) -> String {
|
||||
if library.config().album_column.unwrap_or(true) {
|
||||
if library.cfg.values().album_column.unwrap_or(true) {
|
||||
self.album.to_string()
|
||||
} else {
|
||||
"".to_string()
|
||||
@@ -163,7 +163,7 @@ impl ListItem for Track {
|
||||
|
||||
fn display_right(&self, library: Arc<Library>) -> String {
|
||||
let saved = if library.is_saved_track(&Playable::Track(self.clone())) {
|
||||
if library.use_nerdfont {
|
||||
if library.cfg.values().use_nerdfont.unwrap_or(false) {
|
||||
"\u{f62b} "
|
||||
} else {
|
||||
"✓ "
|
||||
|
||||
Reference in New Issue
Block a user