diff --git a/src/config.rs b/src/config.rs index 4cc4b439d..7e91987d1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,6 +2,7 @@ use crate::app::Message; use crate::i18n::{UnitSystem, unit_system}; use crate::services::upower::PeripheralDeviceKind; use crate::utils::celsius_to_fahrenheit; +use crate::utils::themes::PrebuiltTheme; use hex_color::HexColor; use iced::futures::StreamExt; use iced::{Color, Subscription, futures::SinkExt, stream::channel, theme::palette}; @@ -1033,11 +1034,9 @@ impl Default for MenuAppearance { } } -#[derive(Deserialize, Clone, Debug)] -#[serde(default)] +#[derive(Clone, Debug)] pub struct Appearance { pub font_name: Option, - #[serde(deserialize_with = "scale_factor_deserializer")] pub scale_factor: f64, pub bar: BarAppearance, pub menu: MenuAppearance, @@ -1049,11 +1048,85 @@ pub struct Appearance { pub text_color: AppearanceColor, pub workspace_colors: Vec, pub special_workspace_colors: Option>, - /// Blur the wallpaper behind ashell's translucent surfaces via - /// `ext-background-effect-v1`. No-op where the protocol is unsupported. pub blur: BlurMode, + pub theme: Option, } +// 2. Define a Shadow Struct that replicates your fields as raw Serde values +#[derive(Deserialize)] +struct AppearanceShadow { + font_name: Option, + scale_factor: Option, // Intercepted for your custom helper + bar: Option, + menu: Option, + background_color: Option, + primary_color: Option, + success_color: Option, + warning_color: Option, + danger_color: Option, + text_color: Option, + workspace_colors: Option>, + special_workspace_colors: Option>, + blur: Option, + theme: Option, +} +impl<'de> Deserialize<'de> for Appearance { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let shadow = AppearanceShadow::deserialize(deserializer)?; + let mut app = Appearance::default(); + if let Some(ref theme_name) = shadow.theme { + app.theme = Some(theme_name.clone()); + if let Some(theme) = PrebuiltTheme::parse(theme_name) { + theme.apply(&mut app); + } + } + if let Some(font) = shadow.font_name { + app.font_name = Some(font); + } + if let Some(bar_cfg) = shadow.bar { + app.bar = bar_cfg; + } + if let Some(menu_cfg) = shadow.menu { + app.menu = menu_cfg; + } + if let Some(bg_color) = shadow.background_color { + app.background_color = bg_color; + } + if let Some(primary) = shadow.primary_color { + app.primary_color = primary; + } + if let Some(success) = shadow.success_color { + app.success_color = success; + } + if let Some(warning) = shadow.warning_color { + app.warning_color = warning; + } + if let Some(danger) = shadow.danger_color { + app.danger_color = danger; + } + if let Some(text) = shadow.text_color { + app.text_color = text; + } + if let Some(workspaces) = shadow.workspace_colors { + app.workspace_colors = workspaces; + } + if let Some(special) = shadow.special_workspace_colors { + app.special_workspace_colors = Some(special); + } + if let Some(blur_cfg) = shadow.blur { + app.blur = blur_cfg; + } + if let Some(raw_scale) = shadow.scale_factor { + app.scale_factor = + scale_factor_deserializer(raw_scale).map_err(serde::de::Error::custom)?; + } + + Ok(app) + } +} /// When to ask the compositor for background blur. #[derive(Deserialize, Default, Clone, Copy, Debug, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -1152,6 +1225,7 @@ impl Default for Appearance { ], special_workspace_colors: None, blur: BlurMode::default(), + theme: None, } } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 9bcc270c9..141fde54c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,7 +4,7 @@ use unicode_segmentation::UnicodeSegmentation; pub mod launcher; pub mod remote_value; - +pub mod themes; #[derive(Debug, Clone, Copy)] pub enum IndicatorState { Normal, diff --git a/src/utils/themes.rs b/src/utils/themes.rs new file mode 100644 index 000000000..fa84092fb --- /dev/null +++ b/src/utils/themes.rs @@ -0,0 +1,107 @@ +use hex_color::HexColor; +use iced::{Color, Theme}; + +use crate::config::{Appearance, AppearanceColor}; + +pub struct PrebuiltTheme { + pub theme: Theme, +} +impl PrebuiltTheme { + pub fn parse(theme_name: &String) -> Option { + let theme = match theme_name + .to_lowercase() + .replace(|c| [' ', '-', '_'].contains(&c), "") + .as_str() + { + "light" => Some(Theme::Light), + "dark" => Some(Theme::Dark), + "dracula" => Some(Theme::Dracula), + "nord" => Some(Theme::Nord), + "solarizedlight" => Some(Theme::SolarizedLight), + "solarizeddark" => Some(Theme::SolarizedDark), + "gruvboxlight" => Some(Theme::GruvboxLight), + "gruvboxdark" => Some(Theme::GruvboxDark), + "catppuccinlatte" => Some(Theme::CatppuccinLatte), + "catppuccinfrappe" => Some(Theme::CatppuccinFrappe), + "catppuccinmacchiato" => Some(Theme::CatppuccinMacchiato), + "catppuccinmocha" | "catppuccin" => Some(Theme::CatppuccinMocha), + "tokyonight" => Some(Theme::TokyoNight), + "tokyonightstorm" => Some(Theme::TokyoNightStorm), + "tokyonightlight" => Some(Theme::TokyoNightLight), + "kanagawawave" => Some(Theme::KanagawaWave), + "kanagawadragon" => Some(Theme::KanagawaDragon), + "kanagawalotus" => Some(Theme::KanagawaLotus), + "moonfly" => Some(Theme::Moonfly), + "nightfly" => Some(Theme::Nightfly), + "oxocarbon" => Some(Theme::Oxocarbon), + "ferra" => Some(Theme::Ferra), + //Can add more using the custom theme in iced + _ => None, + }; + if let Some(t) = theme { + return Some(PrebuiltTheme { theme: t }); + } + return None; + } + pub fn apply(&self, app: &mut Appearance) { + let extended = self.theme.extended_palette(); + let background = extended.background; + app.background_color = crate::config::BackgroundAppearanceColor::Complete { + base: color_to_hex_color(background.base.color), + weakest: Some(color_to_hex_color(background.weakest.color)), + weaker: Some(color_to_hex_color(background.weaker.color)), + weak: Some(color_to_hex_color(background.weak.color)), + neutral: Some(color_to_hex_color(background.neutral.color)), + strong: Some(color_to_hex_color(background.strong.color)), + stronger: Some(color_to_hex_color(background.stronger.color)), + strongest: Some(color_to_hex_color(background.strongest.color)), + text: Some(color_to_hex_color(background.base.text)), + }; + let primary = extended.primary; + app.primary_color = AppearanceColor::Complete { + base: color_to_hex_color(primary.base.color), + strong: Some(color_to_hex_color(primary.strong.color)), + weak: Some(color_to_hex_color(primary.weak.color)), + text: Some(color_to_hex_color(primary.base.color)), + }; + let success = extended.success; + app.success_color = AppearanceColor::Complete { + base: color_to_hex_color(success.base.color), + strong: Some(color_to_hex_color(success.strong.color)), + weak: Some(color_to_hex_color(success.weak.color)), + text: Some(color_to_hex_color(success.base.color)), + }; + let warning = extended.warning; + app.warning_color = AppearanceColor::Complete { + base: color_to_hex_color(warning.base.color), + strong: Some(color_to_hex_color(warning.strong.color)), + weak: Some(color_to_hex_color(warning.weak.color)), + text: Some(color_to_hex_color(warning.base.color)), + }; + let danger = extended.danger; + app.danger_color = AppearanceColor::Complete { + base: color_to_hex_color(danger.base.color), + strong: Some(color_to_hex_color(danger.strong.color)), + weak: Some(color_to_hex_color(danger.weak.color)), + text: Some(color_to_hex_color(danger.base.color)), + }; + app.text_color = AppearanceColor::Complete { + base: color_to_hex_color(primary.base.color), + strong: Some(color_to_hex_color(primary.strong.color)), + weak: Some(color_to_hex_color(primary.weak.color)), + text: None, + }; + app.workspace_colors = vec![ + AppearanceColor::Simple(color_to_hex_color(primary.base.color)), + AppearanceColor::Simple(color_to_hex_color(success.base.color)), + AppearanceColor::Simple(color_to_hex_color(warning.base.color)), + ]; + } +} +pub fn color_to_hex_color(color: Color) -> HexColor { + let r = (color.r.clamp(0.0, 1.0) * 255.0).round() as u8; + let g = (color.g.clamp(0.0, 1.0) * 255.0).round() as u8; + let b = (color.b.clamp(0.0, 1.0) * 255.0).round() as u8; + let a = (color.a.clamp(0.0, 1.0) * 255.0).round() as u8; + HexColor { r, g, b, a } +}