mirror of
https://github.com/socatlolmeow/drift.git
synced 2026-11-03 11:20:20 +00:00
add: accent color option in config
This commit is contained in:
parent
b31d0226d3
commit
bf11685a03
3 changed files with 141 additions and 44 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
105
src/config.rs
105
src/config.rs
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
src/ui.rs
78
src/ui.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue