add: accent color option in config

This commit is contained in:
socatlolmeow 2026-10-11 18:39:28 +02:00
commit bf11685a03
3 changed files with 141 additions and 44 deletions

View file

@ -165,6 +165,7 @@ pub struct App {
pub volume: u8, pub volume: u8,
pub shuffle: bool, pub shuffle: bool,
pub repeat: RepeatMode, pub repeat: RepeatMode,
pub accent: ratatui::style::Color,
played: HashSet<usize>, played: HashSet<usize>,
fail_streak: u8, fail_streak: u8,
result_cursor: Option<usize>, result_cursor: Option<usize>,
@ -241,6 +242,7 @@ impl App {
volume: p.volume, volume: p.volume,
shuffle: p.shuffle, shuffle: p.shuffle,
repeat: p.repeat, repeat: p.repeat,
accent: cfg.ui.accent,
played: HashSet::new(), played: HashSet::new(),
fail_streak: 0, fail_streak: 0,
result_cursor: None, result_cursor: None,

View file

@ -1,13 +1,33 @@
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
use serde::Deserialize; use ratatui::style::Color;
use serde::de::{self, Visitor};
use serde::{Deserialize, Deserializer};
use crate::model::{RepeatMode, Track}; use crate::model::{RepeatMode, Track};
use crate::session::Session; use crate::session::Session;
use crate::store::{write_atomic, Store}; use crate::store::{write_atomic, Store};
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
where
D: Deserializer<'de>,
{
struct ColorVisitor;
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "a color name (e.g. \"cyan\") or hex value (e.g. \"#FF8800\")")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Color, E> {
Color::from_str(v).map_err(de::Error::custom)
}
}
deserializer.deserialize_str(ColorVisitor)
}
#[derive(Debug, Clone, Copy, Deserialize)] #[derive(Debug, Clone, Copy, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Playback { pub struct Playback {
@ -54,11 +74,29 @@ pub struct Artist {
pub popular_first: bool, pub popular_first: bool,
} }
fn default_accent() -> Color {
Color::Cyan
}
#[derive(Debug, Clone, Copy, Deserialize)]
#[serde(default)]
pub struct Ui {
#[serde(deserialize_with = "deserialize_color")]
pub accent: Color,
}
impl Default for Ui {
fn default() -> Self {
Ui { accent: default_accent() }
}
}
#[derive(Debug, Clone, Default, Deserialize)] #[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
pub playback: Playback, pub playback: Playback,
pub artist: Artist, pub artist: Artist,
pub ui: Ui,
} }
impl Config { impl Config {
@ -66,7 +104,27 @@ impl Config {
Ok(Store::dir()?.join("config.toml")) Ok(Store::dir()?.join("config.toml"))
} }
pub fn render(p: &Playback, a: &Artist) -> String { pub fn render(p: &Playback, a: &Artist, u: &Ui) -> String {
let accent = match u.accent {
Color::Reset => "reset",
Color::Black => "black",
Color::Red => "red",
Color::Green => "green",
Color::Yellow => "yellow",
Color::Blue => "blue",
Color::Magenta => "magenta",
Color::Cyan => "cyan",
Color::Gray => "gray",
Color::DarkGray => "dark-gray",
Color::LightRed => "light-red",
Color::LightGreen => "light-green",
Color::LightYellow => "light-yellow",
Color::LightBlue => "light-blue",
Color::LightMagenta => "light-magenta",
Color::LightCyan => "light-cyan",
Color::White => "white",
_ => "cyan",
};
format!( format!(
"\ "\
# Drift settings. Drift only reads this file and never rewrites it, so your # Drift settings. Drift only reads this file and never rewrites it, so your
@ -84,6 +142,14 @@ volume_step = {vstep:?}
[artist] [artist]
# :artist and ga list the artist's most popular songs first, then more of their songs below # :artist and ga list the artist's most popular songs first, then more of their songs below
popular_first = {popular} popular_first = {popular}
[ui]
# Accent color used throughout the UI.
# Named colors: black, red, green, yellow, blue, magenta, cyan, gray, dark-gray,
# light-red, light-green, light-yellow, light-blue, light-magenta,
# light-cyan, white
# Or use a hex value: \"#FF8800\"
accent = \"{accent}\"
", ",
volume = p.volume, volume = p.volume,
shuffle = p.shuffle, shuffle = p.shuffle,
@ -93,6 +159,7 @@ popular_first = {popular}
seek = p.seek_step, seek = p.seek_step,
vstep = p.volume_step, vstep = p.volume_step,
popular = a.popular_first, popular = a.popular_first,
accent = accent,
) )
} }
@ -107,7 +174,7 @@ popular_first = {popular}
let text = match fs::read_to_string(&path) { let text = match fs::read_to_string(&path) {
Ok(t) => t, Ok(t) => t,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => { Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let _ = Self::write(&Self::render(&Playback::default(), &Artist::default())); let _ = Self::write(&Self::render(&Playback::default(), &Artist::default(), &Ui::default()));
return (Config::default(), None); return (Config::default(), None);
} }
Err(e) => { Err(e) => {
@ -186,7 +253,7 @@ pub fn migrate_legacy() {
let _ = Session::write(&s); let _ = Session::write(&s);
} }
} }
let _ = Config::write(&Config::render(&p, &Artist::default())); let _ = Config::write(&Config::render(&p, &Artist::default(), &Ui::default()));
} }
#[cfg(test)] #[cfg(test)]
@ -195,7 +262,7 @@ mod tests {
#[test] #[test]
fn default_file_parses_back() { fn default_file_parses_back() {
let text = Config::render(&Playback::default(), &Artist::default()); let text = Config::render(&Playback::default(), &Artist::default(), &Ui::default());
let c: Config = toml::from_str(&text).unwrap(); let c: Config = toml::from_str(&text).unwrap();
let d = Playback::default(); let d = Playback::default();
assert_eq!(c.playback.volume, d.volume); assert_eq!(c.playback.volume, d.volume);
@ -238,4 +305,32 @@ mod tests {
p.sanitize(); p.sanitize();
assert_eq!((p.volume, p.seek_step, p.volume_step), (100, 5.0, 100.0)); assert_eq!((p.volume, p.seek_step, p.volume_step), (100, 5.0, 100.0));
} }
#[test]
fn accent_color_parsing() {
// Default is cyan
let c: Config = toml::from_str("").unwrap();
assert_eq!(c.ui.accent, Color::Cyan);
// Named colors
let c: Config = toml::from_str("[ui]\naccent = \"red\"").unwrap();
assert_eq!(c.ui.accent, Color::Red);
let c: Config = toml::from_str("[ui]\naccent = \"green\"").unwrap();
assert_eq!(c.ui.accent, Color::Green);
let c: Config = toml::from_str("[ui]\naccent = \"magenta\"").unwrap();
assert_eq!(c.ui.accent, Color::Magenta);
// Hex value
let c: Config = toml::from_str("[ui]\naccent = \"#FF8800\"").unwrap();
assert_eq!(c.ui.accent, Color::Rgb(255, 136, 0));
// Invalid color is an error
assert!(toml::from_str::<Config>("[ui]\naccent = \"notacolor\"").is_err());
// Roundtrip: render then parse
let u = Ui { accent: Color::Blue };
let text = Config::render(&Playback::default(), &Artist::default(), &u);
let c: Config = toml::from_str(&text).unwrap();
assert_eq!(c.ui.accent, Color::Blue);
}
} }

View file

@ -7,13 +7,12 @@ use ratatui::Frame;
use crate::app::{App, Level, MenuAction, Mode, PlayState, View, NO_KEY}; use crate::app::{App, Level, MenuAction, Mode, PlayState, View, NO_KEY};
use crate::model::{fmt_time, Track}; use crate::model::{fmt_time, Track};
const ACCENT: Color = Color::Cyan;
const SPINNER: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; const SPINNER: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
fn selected_style() -> Style { fn selected_style(accent: Color) -> Style {
Style::new() Style::new()
.fg(Color::Black) .fg(Color::Black)
.bg(ACCENT) .bg(accent)
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
} }
@ -87,7 +86,7 @@ fn draw_tabs(f: &mut Frame, app: &App, area: Rect) {
.areas(area); .areas(area);
f.render_widget( f.render_widget(
Paragraph::new(" Drift").style(Style::new().fg(ACCENT).bold()), Paragraph::new(" Drift").style(Style::new().fg(app.accent).bold()),
name, name,
); );
@ -100,7 +99,7 @@ fn draw_tabs(f: &mut Frame, app: &App, area: Rect) {
Tabs::new(titles) Tabs::new(titles)
.select(app.view.idx()) .select(app.view.idx())
.style(Style::new().fg(Color::Gray)) .style(Style::new().fg(Color::Gray))
.highlight_style(Style::new().fg(Color::Black).bg(ACCENT).bold()) .highlight_style(Style::new().fg(Color::Black).bg(app.accent).bold())
.divider(" "), .divider(" "),
tabs, tabs,
); );
@ -167,7 +166,7 @@ fn track_rows<'a>(
.collect() .collect()
} }
fn track_table<'a>(rows: Vec<Row<'a>>, show_album: bool, block: Block<'a>) -> Table<'a> { fn track_table<'a>(rows: Vec<Row<'a>>, show_album: bool, block: Block<'a>, accent: Color) -> Table<'a> {
let mut header = vec!["", " #", "Title", "Artist"]; let mut header = vec!["", " #", "Title", "Artist"];
let mut widths = vec![ let mut widths = vec![
Constraint::Length(2), Constraint::Length(2),
@ -186,7 +185,7 @@ fn track_table<'a>(rows: Vec<Row<'a>>, show_album: bool, block: Block<'a>) -> Ta
.header(Row::new(header).style(header_style())) .header(Row::new(header).style(header_style()))
.block(block) .block(block)
.column_spacing(1) .column_spacing(1)
.row_highlight_style(selected_style()) .row_highlight_style(selected_style(accent))
} }
fn empty_hint(f: &mut Frame, block: Block, area: Rect, lines: &[&str]) { fn empty_hint(f: &mut Frame, block: Block, area: Rect, lines: &[&str]) {
@ -210,7 +209,7 @@ fn draw_tracks(f: &mut Frame, app: &mut App, area: Rect, view: View) {
}; };
let mut block = Block::bordered() let mut block = Block::bordered()
.title(format!("{title}({}) ", tracks.len())) .title(format!("{title}({}) ", tracks.len()))
.border_style(Style::new().fg(ACCENT)); .border_style(Style::new().fg(app.accent));
if !tracks.is_empty() { if !tracks.is_empty() {
let keys: &[(&str, &str)] = if view == View::Queue { let keys: &[(&str, &str)] = if view == View::Queue {
&[("⏎", "play"), ("x", "remove"), ("J/K", "move"), ("G", "menu"), ("?", "help")] &[("⏎", "play"), ("x", "remove"), ("J/K", "move"), ("G", "menu"), ("?", "help")]
@ -239,7 +238,7 @@ fn draw_tracks(f: &mut Frame, app: &mut App, area: Rect, view: View) {
return empty_hint(f, block, area, hint); return empty_hint(f, block, area, hint);
} }
let rows = track_rows(tracks, playing, show_album); let rows = track_rows(tracks, playing, show_album);
let table = track_table(rows, show_album, block); let table = track_table(rows, show_album, block, app.accent);
let idx = view.idx(); let idx = view.idx();
f.render_stateful_widget(table, area, &mut app.states[idx]); f.render_stateful_widget(table, area, &mut app.states[idx]);
} }
@ -253,7 +252,7 @@ fn draw_open_playlist(f: &mut Frame, app: &mut App, area: Rect, name: &str) {
}; };
let mut block = Block::bordered() let mut block = Block::bordered()
.title(format!(" {name} ({}) ", tracks.len())) .title(format!(" {name} ({}) ", tracks.len()))
.border_style(Style::new().fg(ACCENT)); .border_style(Style::new().fg(app.accent));
if !tracks.is_empty() { if !tracks.is_empty() {
let keys = [("⏎", "play"), ("a", "queue"), ("x", "remove"), ("G", "menu"), ("←", "back")]; let keys = [("⏎", "play"), ("a", "queue"), ("x", "remove"), ("G", "menu"), ("←", "back")];
if let Some(h) = hints(&keys, area.width) { if let Some(h) = hints(&keys, area.width) {
@ -274,7 +273,7 @@ fn draw_open_playlist(f: &mut Frame, app: &mut App, area: Rect, name: &str) {
} }
let show_album = area.width >= 76; let show_album = area.width >= 76;
let rows = track_rows(tracks, playing, show_album); let rows = track_rows(tracks, playing, show_album);
let table = track_table(rows, show_album, block); let table = track_table(rows, show_album, block, app.accent);
f.render_stateful_widget(table, area, &mut app.pl_state); f.render_stateful_widget(table, area, &mut app.pl_state);
} }
@ -289,7 +288,7 @@ fn draw_playlists(f: &mut Frame, app: &mut App, area: Rect) {
let mut block = Block::bordered() let mut block = Block::bordered()
.title(format!(" Playlists ({}) ", app.store.playlists.len())) .title(format!(" Playlists ({}) ", app.store.playlists.len()))
.border_style(Style::new().fg(ACCENT)); .border_style(Style::new().fg(app.accent));
if !app.store.playlists.is_empty() { if !app.store.playlists.is_empty() {
if let Some(h) = hints(&[("⏎", "load"), ("→", "open"), ("G", "menu")], left.width) { if let Some(h) = hints(&[("⏎", "load"), ("→", "open"), ("G", "menu")], left.width) {
block = block.title_bottom(h); block = block.title_bottom(h);
@ -336,7 +335,7 @@ fn draw_playlists(f: &mut Frame, app: &mut App, area: Rect) {
) )
.header(Row::new(vec!["", "Name", "Songs"]).style(header_style())) .header(Row::new(vec!["", "Name", "Songs"]).style(header_style()))
.block(block) .block(block)
.row_highlight_style(selected_style()); .row_highlight_style(selected_style(app.accent));
f.render_stateful_widget(table, left, &mut app.states[View::Playlists.idx()]); f.render_stateful_widget(table, left, &mut app.states[View::Playlists.idx()]);
let sel = app.selected(); let sel = app.selected();
@ -348,7 +347,7 @@ fn draw_playlists(f: &mut Frame, app: &mut App, area: Rect) {
Some(tracks) if !tracks.is_empty() => { Some(tracks) if !tracks.is_empty() => {
let show_album = right.width >= 70; let show_album = right.width >= 70;
let rows = track_rows(tracks, None, show_album); let rows = track_rows(tracks, None, show_album);
f.render_widget(track_table(rows, show_album, preview_block), right); f.render_widget(track_table(rows, show_album, preview_block, app.accent), right);
} }
_ => f.render_widget(preview_block, right), _ => f.render_widget(preview_block, right),
} }
@ -356,8 +355,8 @@ fn draw_playlists(f: &mut Frame, app: &mut App, area: Rect) {
const BARS: [&str; 8] = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]; const BARS: [&str; 8] = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
fn gradient(_t: f64) -> Color { fn gradient(_t: f64, accent: Color) -> Color {
ACCENT accent
} }
fn equalizer(app: &App, n: usize) -> Vec<Span<'static>> { fn equalizer(app: &App, n: usize) -> Vec<Span<'static>> {
@ -373,7 +372,7 @@ fn equalizer(app: &App, n: usize) -> Vec<Span<'static>> {
_ => 0, _ => 0,
}; };
let color = if app.play_state == PlayState::Playing { let color = if app.play_state == PlayState::Playing {
gradient(x / n as f64) gradient(x / n as f64, app.accent)
} else { } else {
Color::DarkGray Color::DarkGray
}; };
@ -386,10 +385,10 @@ fn draw_now_playing(f: &mut Frame, app: &App, area: Rect) {
let active = app.play_state != PlayState::Stopped; let active = app.play_state != PlayState::Stopped;
let mut block = Block::bordered() let mut block = Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(Style::new().fg(if active { ACCENT } else { Color::DarkGray })) .border_style(Style::new().fg(if active { app.accent } else { Color::DarkGray }))
.title(Line::from(vec![ .title(Line::from(vec![
Span::styled(" ♪ ", Style::new().fg(ACCENT).bold()), Span::styled(" ♪ ", Style::new().fg(app.accent).bold()),
Span::styled("Now Playing ", Style::new().fg(ACCENT).bold()), Span::styled("Now Playing ", Style::new().fg(app.accent).bold()),
])); ]));
let mut pending = String::new(); let mut pending = String::new();
@ -410,7 +409,7 @@ fn draw_now_playing(f: &mut Frame, app: &App, area: Rect) {
f.render_widget(block, area); f.render_widget(block, area);
let [l1, l2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(inner); let [l1, l2] = Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(inner);
let on = Style::new().fg(ACCENT).bold(); let on = Style::new().fg(app.accent).bold();
let vol_cells = 5usize; let vol_cells = 5usize;
let vol_fill = ((app.volume as usize * vol_cells) + 50) / 100; let vol_fill = ((app.volume as usize * vol_cells) + 50) / 100;
let mut flags = vec![ let mut flags = vec![
@ -432,7 +431,7 @@ fn draw_now_playing(f: &mut Frame, app: &App, area: Rect) {
]; ];
for i in 0..vol_cells { for i in 0..vol_cells {
flags.push(if i < vol_fill.min(vol_cells) { flags.push(if i < vol_fill.min(vol_cells) {
Span::styled("━", Style::new().fg(gradient(i as f64 / vol_cells as f64))) Span::styled("━", Style::new().fg(gradient(i as f64 / vol_cells as f64, app.accent)))
} else { } else {
Span::styled("─", dim()) Span::styled("─", dim())
}); });
@ -466,7 +465,7 @@ fn draw_now_playing(f: &mut Frame, app: &App, area: Rect) {
(Some(t), true) => { (Some(t), true) => {
spans.push(Span::styled(t.title.clone(), Style::new().fg(Color::White).bold())); spans.push(Span::styled(t.title.clone(), Style::new().fg(Color::White).bold()));
spans.push(Span::styled(" · ", dim())); spans.push(Span::styled(" · ", dim()));
spans.push(Span::styled(t.artist.clone(), Style::new().fg(ACCENT))); spans.push(Span::styled(t.artist.clone(), Style::new().fg(app.accent)));
} }
(Some(t), false) => spans.push(Span::styled( (Some(t), false) => spans.push(Span::styled(
format!("Last played: {} — {}", t.title, t.artist), format!("Last played: {} — {}", t.title, t.artist),
@ -497,7 +496,7 @@ fn draw_now_playing(f: &mut Frame, app: &App, area: Rect) {
line.push(if !active || app.duration <= 0.0 { line.push(if !active || app.duration <= 0.0 {
Span::styled("─", dim()) Span::styled("─", dim())
} else if i < head { } else if i < head {
Span::styled("━", Style::new().fg(gradient(i as f64 / bar_w.max(1) as f64))) Span::styled("━", Style::new().fg(gradient(i as f64 / bar_w.max(1) as f64, app.accent)))
} else if i == head { } else if i == head {
Span::styled("●", Style::new().fg(Color::White).bold()) Span::styled("●", Style::new().fg(Color::White).bold())
} else { } else {
@ -580,8 +579,8 @@ fn draw_info(f: &mut Frame, app: &App, area: Rect) {
Paragraph::new(lines).block( Paragraph::new(lines).block(
Block::bordered() Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(Style::new().fg(ACCENT)) .border_style(Style::new().fg(app.accent))
.title(Span::styled(" ♪ Song info ", Style::new().fg(ACCENT).bold())) .title(Span::styled(" ♪ Song info ", Style::new().fg(app.accent).bold()))
.title_bottom(Line::styled(" any key to close ", dim()).right_aligned()), .title_bottom(Line::styled(" any key to close ", dim()).right_aligned()),
), ),
popup, popup,
@ -676,11 +675,11 @@ fn draw_palette(f: &mut Frame, app: &App, area: Rect) {
}; };
let block = Block::bordered() let block = Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(Style::new().fg(if search { ACCENT } else { ACCENT })) .border_style(Style::new().fg(if search { app.accent } else { app.accent }))
.title(Span::styled( .title(Span::styled(
title, title,
Style::new() Style::new()
.fg(if search { ACCENT } else { ACCENT }) .fg(if search { app.accent } else { app.accent })
.bold(), .bold(),
)) ))
.title_bottom(Line::styled(footer, dim()).right_aligned()); .title_bottom(Line::styled(footer, dim()).right_aligned());
@ -704,7 +703,7 @@ fn draw_palette(f: &mut Frame, app: &App, area: Rect) {
Paragraph::new(Line::from(vec![ Paragraph::new(Line::from(vec![
Span::styled( Span::styled(
prompt, prompt,
Style::new().fg(Color::Black).bg(if search { ACCENT } else { ACCENT }).bold(), Style::new().fg(Color::Black).bg(if search { app.accent } else { app.accent }).bold(),
), ),
Span::raw(" "), Span::raw(" "),
Span::styled(shown, Style::new().fg(Color::White)), Span::styled(shown, Style::new().fg(Color::White)),
@ -737,7 +736,7 @@ fn draw_palette(f: &mut Frame, app: &App, area: Rect) {
Line::styled(" ⏎ run", dim()) Line::styled(" ⏎ run", dim())
} else { } else {
Line::from(vec![ Line::from(vec![
Span::styled(format!(" {first_word} "), Style::new().fg(ACCENT).bold()), Span::styled(format!(" {first_word} "), Style::new().fg(app.accent).bold()),
Span::styled(d, dim()), Span::styled(d, dim()),
]) ])
}); });
@ -752,12 +751,12 @@ fn draw_palette(f: &mut Frame, app: &App, area: Rect) {
let text: String = text.chars().take(width).collect(); let text: String = text.chars().take(width).collect();
lines.push(if Some(i) == sel { lines.push(if Some(i) == sel {
let pad = width.saturating_sub(text.chars().count()); let pad = width.saturating_sub(text.chars().count());
Line::styled(format!("{text}{}", " ".repeat(pad)), selected_style()) Line::styled(format!("{text}{}", " ".repeat(pad)), selected_style(app.accent))
} else { } else {
Line::from(vec![ Line::from(vec![
Span::styled( Span::styled(
text.chars().take(11).collect::<String>(), text.chars().take(11).collect::<String>(),
Style::new().fg(ACCENT), Style::new().fg(app.accent),
), ),
Span::styled(text.chars().skip(11).collect::<String>(), dim()), Span::styled(text.chars().skip(11).collect::<String>(), dim()),
]) ])
@ -799,14 +798,15 @@ fn draw_menu(f: &mut Frame, app: &App, area: Rect) {
}; };
let block = Block::bordered() let block = Block::bordered()
.border_type(BorderType::Rounded) .border_type(BorderType::Rounded)
.border_style(Style::new().fg(ACCENT)) .border_style(Style::new().fg(app.accent))
.title(Span::styled(title, Style::new().fg(ACCENT).bold())) .title(Span::styled(title, Style::new().fg(app.accent).bold()))
.title_bottom(Line::styled(footer, dim()).right_aligned()); .title_bottom(Line::styled(footer, dim()).right_aligned());
let inner = block.inner(popup); let inner = block.inner(popup);
f.render_widget(Clear, popup); f.render_widget(Clear, popup);
f.render_widget(block, popup); f.render_widget(block, popup);
let width = inner.width as usize; let width = inner.width as usize;
let accent = app.accent;
let lines: Vec<Line> = items let lines: Vec<Line> = items
.iter() .iter()
.enumerate() .enumerate()
@ -821,7 +821,7 @@ fn draw_menu(f: &mut Frame, app: &App, area: Rect) {
} }
let text: String = text.chars().take(width).collect(); let text: String = text.chars().take(width).collect();
let pad = width.saturating_sub(text.chars().count()); let pad = width.saturating_sub(text.chars().count());
return Line::styled(format!("{text}{}", " ".repeat(pad)), selected_style()); return Line::styled(format!("{text}{}", " ".repeat(pad)), selected_style(accent));
} }
let danger = matches!( let danger = matches!(
it.action, it.action,
@ -966,10 +966,10 @@ fn wrap_words(text: &str, width: usize) -> Vec<String> {
out out
} }
fn help_lines(inner_w: usize) -> Vec<Line<'static>> { fn help_lines(inner_w: usize, accent: Color) -> Vec<Line<'static>> {
let key_w = HELP_KEY_W.min(inner_w / 2).max(1); let key_w = HELP_KEY_W.min(inner_w / 2).max(1);
let desc_w = inner_w.saturating_sub(key_w); let desc_w = inner_w.saturating_sub(key_w);
let key_style = Style::new().fg(ACCENT); let key_style = Style::new().fg(accent);
let mut lines: Vec<Line<'static>> = Vec::new(); let mut lines: Vec<Line<'static>> = Vec::new();
for (k, d) in HELP { for (k, d) in HELP {
@ -1025,7 +1025,7 @@ fn draw_artist_picker(f: &mut Frame, app: &App, area: Rect) {
.map(|(i, name)| { .map(|(i, name)| {
let text = format!(" {} {name}", i + 1); let text = format!(" {} {name}", i + 1);
if i == app.artist_sel { if i == app.artist_sel {
Line::styled(text, selected_style()) Line::styled(text, selected_style(app.accent))
} else { } else {
Line::raw(text) Line::raw(text)
} }
@ -1045,7 +1045,7 @@ fn draw_artist_picker(f: &mut Frame, app: &App, area: Rect) {
fn draw_help(f: &mut Frame, app: &mut App, area: Rect) { fn draw_help(f: &mut Frame, app: &mut App, area: Rect) {
let w = area.width.min(80); let w = area.width.min(80);
let lines = help_lines(w.saturating_sub(2) as usize); let lines = help_lines(w.saturating_sub(2) as usize, app.accent);
let h = (lines.len() as u16).saturating_add(2).min(area.height); let h = (lines.len() as u16).saturating_add(2).min(area.height);
let popup = Rect { let popup = Rect {
x: area.x + (area.width - w) / 2, x: area.x + (area.width - w) / 2,