diff --git a/Cargo.lock b/Cargo.lock index 866b7f3b..deff5a81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3907,6 +3907,7 @@ dependencies = [ "threadpool", "time 0.3.41", "time-humanize", + "toml 0.8.22", "ureq 3.0.11", "url", "winres", @@ -5346,9 +5347,16 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow 0.7.9", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tower-service" version = "0.3.3" diff --git a/psst-gui/Cargo.toml b/psst-gui/Cargo.toml index 769d5572..c5398b20 100644 --- a/psst-gui/Cargo.toml +++ b/psst-gui/Cargo.toml @@ -53,6 +53,7 @@ raw-window-handle = "0.5.2" # Must stay compatible with Druid souvlaki = { version = "0.8.2", default-features = false, features = ["use_zbus"] } sanitize_html = "0.9.0" rustfm-scrobble = "1.1.1" +toml = "0.8.22" [target.'cfg(windows)'.build-dependencies] winres = { version = "0.1.12" } image = { version = "0.25.6" } diff --git a/psst-gui/src/data/config.rs b/psst-gui/src/data/config.rs index 99010ce0..419f1bb8 100644 --- a/psst-gui/src/data/config.rs +++ b/psst-gui/src/data/config.rs @@ -277,6 +277,8 @@ impl Default for AudioQuality { pub enum Theme { Light, Dark, + GruvboxDark, + GruvboxLight, } impl Default for Theme { diff --git a/psst-gui/src/ui/preferences.rs b/psst-gui/src/ui/preferences.rs index f3c69dd4..7a4d2c10 100644 --- a/psst-gui/src/ui/preferences.rs +++ b/psst-gui/src/ui/preferences.rs @@ -186,8 +186,13 @@ fn general_tab_widget() -> impl Widget { .with_child(Label::new("Theme").with_font(theme::UI_FONT_MEDIUM)) .with_spacer(theme::grid(2.0)) .with_child( - RadioGroup::column(vec![("Light", Theme::Light), ("Dark", Theme::Dark)]) - .lens(AppState::config.then(Config::theme)), + RadioGroup::column(vec![ + ("Light", Theme::Light), + ("Dark", Theme::Dark), + ("Gruvbox Light", Theme::GruvboxLight), + ("Gruvbox Dark", Theme::GruvboxDark), + ]) + .lens(AppState::config.then(Config::theme)), ); col = col.with_spacer(theme::grid(1.5)); diff --git a/psst-gui/src/ui/theme.rs b/psst-gui/src/ui/theme.rs index 0690ba5a..aaad032f 100644 --- a/psst-gui/src/ui/theme.rs +++ b/psst-gui/src/ui/theme.rs @@ -1,6 +1,9 @@ +use std::fs; + use druid::{Color, Env, FontDescriptor, FontFamily, FontWeight, Insets, Key, Size}; pub use druid::theme::*; +use serde::Deserialize; use crate::data::{AppState, Theme}; @@ -46,6 +49,8 @@ pub fn setup(env: &mut Env, state: &AppState) { match state.config.theme { Theme::Light => setup_light_theme(env), Theme::Dark => setup_dark_theme(env), + Theme::GruvboxDark => setup_gruvbox_dark_theme(env), + Theme::GruvboxLight => setup_gruvbox_light_theme(env), }; env.set(WINDOW_BACKGROUND_COLOR, env.get(GREY_700)); @@ -69,6 +74,14 @@ pub fn setup(env: &mut Env, state: &AppState) { env.set(BUTTON_LIGHT, env.get(GREY_600)); env.set(BUTTON_DARK, env.get(GREY_700)); } + Theme::GruvboxDark => { + env.set(BUTTON_LIGHT, env.get(GREY_700)); + env.set(BUTTON_DARK, env.get(GREY_600)); + } + Theme::GruvboxLight => { + env.set(BUTTON_LIGHT, env.get(GREY_600)); + env.set(BUTTON_DARK, env.get(GREY_700)); + } } env.set(BORDER_LIGHT, env.get(GREY_400)); @@ -165,3 +178,102 @@ fn setup_dark_theme(env: &mut Env) { env.set(LINK_ACTIVE_COLOR, Color::rgba(1.0, 1.0, 1.0, 0.025)); env.set(LINK_COLD_COLOR, Color::rgba(1.0, 1.0, 1.0, 0.0)); } + +fn setup_gruvbox_light_theme(env: &mut Env) { + // Gruvbox Light palette + // https://github.com/morhetz/gruvbox + env.set(GREY_000, Color::rgb8(0x3c, 0x38, 0x36)); // fg0 + env.set(GREY_100, Color::rgb8(0x50, 0x49, 0x45)); // fg1 + env.set(GREY_200, Color::rgb8(0x66, 0x5c, 0x54)); // fg2 + env.set(GREY_300, Color::rgb8(0xa8, 0x99, 0x84)); // gray + env.set(GREY_400, Color::rgb8(0xd5, 0xc4, 0xa1)); // bg3 + env.set(GREY_500, Color::rgb8(0xeb, 0xdb, 0xb2)); // bg2 + env.set(GREY_600, Color::rgb8(0xf2, 0xe5, 0xbc)); // bg1 + env.set(GREY_700, Color::rgb8(0xfb, 0xf1, 0xc7)); // bg0 + + env.set(BLUE_100, Color::rgb8(0x45, 0x85, 0x88)); // blue + env.set(BLUE_200, Color::rgb8(0x07, 0x66, 0x78)); // dark blue + + env.set(RED, Color::rgb8(0xcc, 0x24, 0x1d)); // red + + env.set(LINK_HOT_COLOR, Color::rgba8(0x45, 0x85, 0x88, 0x80)); // blue, 50% alpha + env.set(LINK_ACTIVE_COLOR, Color::rgba8(0x45, 0x85, 0x88, 0x40)); // blue, 25% alpha + env.set(LINK_COLD_COLOR, Color::rgba8(0x45, 0x85, 0x88, 0x00)); // blue, 0% alpha +} + +fn setup_gruvbox_dark_theme(env: &mut Env) { + // Gruvbox Dark palette + // https://github.com/morhetz/gruvbox + env.set(GREY_000, Color::rgb8(0xeb, 0xdb, 0xb2)); // fg0 + env.set(GREY_100, Color::rgb8(0xd5, 0xc4, 0xa1)); // fg1 + env.set(GREY_200, Color::rgb8(0xbd, 0xae, 0x93)); // fg2 + env.set(GREY_300, Color::rgb8(0xa8, 0x99, 0x84)); // gray + env.set(GREY_400, Color::rgb8(0x66, 0x5c, 0x54)); // bg4 + env.set(GREY_500, Color::rgb8(0x3c, 0x38, 0x36)); // bg2 + env.set(GREY_600, Color::rgb8(0x28, 0x28, 0x28)); // bg1 + env.set(GREY_700, Color::rgb8(0x1d, 0x20, 0x21)); // bg0_h + + env.set(BLUE_100, Color::rgb8(0x45, 0x85, 0x88)); // blue + env.set(BLUE_200, Color::rgb8(0x07, 0x66, 0x78)); // dark blue + + env.set(RED, Color::rgb8(0xcc, 0x24, 0x1d)); // red + + env.set(LINK_HOT_COLOR, Color::rgba8(0x45, 0x85, 0x88, 0x80)); // blue, 50% alpha + env.set(LINK_ACTIVE_COLOR, Color::rgba8(0x45, 0x85, 0x88, 0x40)); // blue, 25% alpha + env.set(LINK_COLD_COLOR, Color::rgba8(0x45, 0x85, 0x88, 0x00)); // blue, 0% alpha +} + +fn load_colorscheme(path: &str) -> Option { + let content = fs::read_to_string(path).ok()?; + toml::from_str(&content).ok() +} + +fn setup_custom_theme(env: &mut Env, scheme: &ColorScheme) { + env.set(GREY_000, Color::from_hex_str(&scheme.grey_000).unwrap()); + env.set(GREY_100, Color::from_hex_str(&scheme.grey_100).unwrap()); + env.set(GREY_200, Color::from_hex_str(&scheme.grey_200).unwrap()); + env.set(GREY_300, Color::from_hex_str(&scheme.grey_300).unwrap()); + env.set(GREY_400, Color::from_hex_str(&scheme.grey_400).unwrap()); + env.set(GREY_500, Color::from_hex_str(&scheme.grey_500).unwrap()); + env.set(GREY_600, Color::from_hex_str(&scheme.grey_600).unwrap()); + env.set(GREY_700, Color::from_hex_str(&scheme.grey_700).unwrap()); + + env.set(BLUE_100, Color::from_hex_str(&scheme.blue_100).unwrap()); + env.set(BLUE_200, Color::from_hex_str(&scheme.blue_200).unwrap()); + + env.set(RED, Color::from_hex_str(&scheme.red).unwrap()); + + env.set( + LINK_HOT_COLOR, + Color::from_hex_str(&scheme.link_hot_color).unwrap(), + ); + env.set( + LINK_ACTIVE_COLOR, + Color::from_hex_str(&scheme.link_active_color).unwrap(), + ); + env.set( + LINK_COLD_COLOR, + Color::from_hex_str(&scheme.link_cold_color).unwrap(), + ); +} + +#[derive(Deserialize, Debug)] +pub struct ColorScheme { + pub grey_000: String, + pub grey_100: String, + pub grey_200: String, + pub grey_300: String, + pub grey_400: String, + pub grey_500: String, + pub grey_600: String, + pub grey_700: String, + + pub blue_100: String, + pub blue_200: String, + + pub red: String, + + pub link_hot_color: String, + pub link_active_color: String, + pub link_cold_color: String, +}