From d6b87a022284322334ea2ca6445fe74f30ba77aa Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 30 Jan 2024 19:20:35 -0500 Subject: [PATCH] chore: update gtk4-output for the theme to set adwaita named colors This is still a bit incomplete, and some apps use their own custom variables as well, for example the text editor. --- Cargo.toml | 4 + cosmic-config/Cargo.toml | 2 +- cosmic-theme/Cargo.toml | 3 + cosmic-theme/src/output/gtk4_output.rs | 319 +++++++++++++------------ cosmic-theme/src/output/mod.rs | 7 +- 5 files changed, 175 insertions(+), 160 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5794963a..e906a0b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,6 +150,10 @@ members = [ ] exclude = ["examples/design-demo", "iced"] +[workspace.dependencies] +dirs = "5.0.1" + + [patch."https://github.com/pop-os/libcosmic"] libcosmic = { path = "./" } diff --git a/cosmic-config/Cargo.toml b/cosmic-config/Cargo.toml index 68af584e..e98ec14b 100644 --- a/cosmic-config/Cargo.toml +++ b/cosmic-config/Cargo.toml @@ -13,7 +13,6 @@ subscription = ["iced_futures"] zbus = { version = "3.14.1", default-features = false, optional = true } atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" } calloop = { version = "0.12.2", optional = true } -dirs = "5.0.1" notify = "6.0.0" ron = "0.8.0" serde = "1.0.152" @@ -23,6 +22,7 @@ iced_futures = { path = "../iced/futures/", default-features = false, optional = once_cell = "1.19.0" cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", branch = "cosmic-settings-daemon", optional = true } futures-util = { version = "0.3", optional = true } +dirs.workspace = true [target.'cfg(unix)'.dependencies] xdg = "2.1" diff --git a/cosmic-theme/Cargo.toml b/cosmic-theme/Cargo.toml index 0ded5411..5acc85ab 100644 --- a/cosmic-theme/Cargo.toml +++ b/cosmic-theme/Cargo.toml @@ -11,6 +11,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = [] +gtk4-output = [] no-default = [] theme-from-image = ["kmeans_colors", "image"] @@ -24,3 +25,5 @@ ron = "0.8" lazy_static = "1.4.0" csscolorparser = {version = "0.6.2", features = ["serde"]} cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription", "macro"] } +dirs.workspace = true +thiserror = "1.0.5" diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 4472f6eb..efc6bea5 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -1,187 +1,200 @@ -use crate::{ - model::{Accent, Container, ContainerType, Destructive, Widget}, - Hex, Theme, NAME, -}; -use anyhow::{bail, Result}; -use palette::Srgba; -use serde::{de::DeserializeOwned, Serialize}; -use std::{fmt, fs::File, io::prelude::*, path::PathBuf}; +use crate::{composite::over, Component, Theme}; +use palette::{rgb::Rgba, Srgba}; +use std::{fs::File, io::prelude::*}; +use thiserror::Error; -pub(crate) const CSS_DIR: &'static str = "css"; -pub(crate) const THEME_DIR: &'static str = "themes"; - -/// Trait for outputting the Theme variables as Gtk4CSS -pub trait Gtk4Output { - /// turn the theme into css - fn as_css(&self) -> String; - /// Serialize the theme as RON and write the CSS to the appropriate directories - /// Should be written in the XDG data directory for cosmic-theme - fn write(&self) -> Result<()>; +#[derive(Error, Debug)] +pub enum OutputError { + #[error("IO Error: {0}")] + Io(std::io::Error), + #[error("Missing config directory")] + MissingConfigDir, } -impl Gtk4Output for Theme -where - C: Clone - + fmt::Debug - + Default - + Into - + Into - + From - + Serialize - + DeserializeOwned, -{ - fn as_css(&self) -> String { +impl Theme { + /// turn the theme into css + pub fn as_gtk4(&self) -> String { let Self { background, primary, secondary, accent, destructive, + warning, + success, .. } = self; - let mut css = String::new(); - css.push_str(&background.as_css()); - css.push_str(&primary.as_css()); - css.push_str(&secondary.as_css()); - css.push_str(&accent.as_css()); - css.push_str(&destructive.as_css()); + let window_bg = to_hex(background.base); + let window_fg = to_hex(background.on); + + let view_bg = to_hex(primary.base); + let view_fg = to_hex(primary.on); + + let headerbar_bg = to_hex(background.base); + let headerbar_fg = to_hex(background.on); + let headerbar_border_color = to_hex(background.divider); + // TODO undefined + // headerbar_darker_shade_color + // headerbar_shade_color + + // TODO confirm with design + let sidebar_bg = to_hex(primary.base); + let sidebar_fg = to_hex(primary.on); + let sidebar_shade = to_hex(if self.is_dark { + Rgba::new(0.0, 0.0, 0.0, 0.08) + } else { + Rgba::new(0.0, 0.0, 0.0, 0.32) + }); + let backdrop_overlay = Srgba::new(1.0, 1.0, 1.0, if self.is_dark { 0.08 } else { 0.32 }); + let sidebar_backdrop = to_hex(over(backdrop_overlay, primary.base)); + + let secondary_sidebar_bg = to_hex(secondary.base); + let secondary_sidebar_fg = to_hex(secondary.on); + let secondary_sidebar_shade = to_hex(if self.is_dark { + Rgba::new(0.0, 0.0, 0.0, 0.08) + } else { + Rgba::new(0.0, 0.0, 0.0, 0.32) + }); + let secondary_sidebar_backdrop = to_hex(over(backdrop_overlay, secondary.base)); + + // TODO undefined + let headerbar_backdrop = to_hex(background.base); + // TODO undefined + // let headerbar_shade = to_hex(background.base); + + let card_bg = to_hex(background.component.base); + let card_fg = to_hex(background.component.on); + // TODO undefined + // let card_shade = to_hex(background.component.base); + + // TODO undefined + let thumbnail_bg = to_hex(background.component.base); + let thumbnail_fg = to_hex(background.component.on); + + let dialog_bg = to_hex(primary.base); + let dialog_fg = to_hex(primary.on); + + let popover_bg = to_hex(background.component.base); + let popover_fg = to_hex(background.component.on); + + let shade = to_hex(if self.is_dark { + Rgba::new(0.0, 0.0, 0.0, 0.32) + } else { + Rgba::new(0.0, 0.0, 0.0, 0.08) + }); + + // TODO undefined + let mut inverted_bg_divider = background.base; + inverted_bg_divider.alpha = 0.5; + let scrollbar_outline = to_hex(inverted_bg_divider); + + // TODO define currentColor + // let borders = to_hex(if self.is_high_contrast { + // Rgba::new(0.0, 0.0, 0.0, 0.2) + // } else { + // Rgba::new(0.0, 0.0, 0.0, 0.5) + // }); + + let mut css = format! {r#" +@define-color window_bg_color #{window_bg}; +@define-color window_fg_color #{window_fg}; + +@define-color view_bg_color #{view_bg}; +@define-color view_fg_color #{view_fg}; + +@define-color headerbar_bg_color #{headerbar_bg}; +@define-color headerbar_fg_color #{headerbar_fg}; +@define-color headerbar_border_color_color #{headerbar_border_color}; +@define-color headerbar_backdrop_color #{headerbar_backdrop}; + +@define-color sidebar_bg_color #{sidebar_bg}; +@define-color sidebar_fg_color #{sidebar_fg}; +@define-color sidebar_shade_color #{sidebar_shade}; +@define-color sidebar_backdrop_color #{sidebar_backdrop}; + +@define-color secondary_sidebar_bg_color #{secondary_sidebar_bg}; +@define-color secondary_sidebar_fg_color #{secondary_sidebar_fg}; +@define-color secondary_sidebar_shade_color #{secondary_sidebar_shade}; +@define-color secondary_sidebar_backdrop_color #{secondary_sidebar_backdrop}; + +@define-color card_bg_color #{card_bg}; +@define-color card_fg_color #{card_fg}; + +@define-color thumbnail_bg_color #{thumbnail_bg}; +@define-color thumbnail_fg_color #{thumbnail_fg}; + +@define-color dialog_bg_color #{dialog_bg}; +@define-color dialog_fg_color #{dialog_fg}; + +@define-color popover_bg_color #{popover_bg}; +@define-color popover_fg_color #{popover_fg}; + +@define-color shade_color #{shade}; +@define-color scrollbar_outline_color #{scrollbar_outline}; + "#}; + + css.push_str(&component_gtk4_css("accent", accent)); + css.push_str(&component_gtk4_css("destructive", destructive)); + css.push_str(&component_gtk4_css("warning", warning)); + css.push_str(&component_gtk4_css("success", success)); + css.push_str(&component_gtk4_css("accent", accent)); + css.push_str(&component_gtk4_css("error", destructive)); css } - fn write(&self) -> Result<()> { - // TODO sass -> css - let ron_str = ron::ser::to_string_pretty(self, Default::default())?; - let css_str = self.as_css(); + /// write the CSS to the appropriate directory + /// Should be written in the XDG config directory for gtk-4.0 + /// + /// # Errors + /// + /// Returns an `OutputError` if there is an error writing the CSS file. + pub fn write_gtk4(&self) -> Result<(), OutputError> { + let css_str = self.as_gtk4(); + let Some(config_dir) = dirs::config_dir() else { + return Err(OutputError::MissingConfigDir); + }; - let ron_path: PathBuf = [NAME, THEME_DIR].iter().collect(); - let css_path: PathBuf = [NAME, CSS_DIR].iter().collect(); - - let ron_dirs = xdg::BaseDirectories::with_prefix(ron_path)?; - let css_dirs = xdg::BaseDirectories::with_prefix(css_path)?; - - let ron_name = format!("{}.ron", &self.name); - let css_name = format!("{}.css", &self.name); - - if let Ok(p) = ron_dirs.place_data_file(ron_name) { - let mut f = File::create(p)?; - f.write_all(ron_str.as_bytes())?; + let name = if self.is_dark { + "dark.css" } else { - bail!("Failed to write RON theme.") + "light.css" + }; + + let config_dir = config_dir.join("gtk-4.0").join("cosmic"); + if !config_dir.exists() { + std::fs::create_dir_all(&config_dir).map_err(OutputError::Io)?; } - if let Ok(p) = css_dirs.place_data_file(css_name) { - let mut f = File::create(p)?; - f.write_all(css_str.as_bytes())?; - } else { - bail!("Failed to write RON theme.") - } + let mut file = File::create(config_dir.join(name)).map_err(OutputError::Io)?; + file.write_all(css_str.as_bytes()) + .map_err(OutputError::Io)?; Ok(()) } } /// Trait for converting theme data into gtk4 CSS -pub trait AsGtk4Css -where - C: Copy + Into + From, -{ +pub trait AsGtk4Css { /// function for converting theme data into gtk4 CSS fn as_css(&self) -> String; } -impl AsGtk4Css for Container -where - C: Copy + Clone + fmt::Debug + Default + Into + From + fmt::Display, -{ - fn as_css(&self) -> String { - let Self { - prefix, - container, - container_component, - container_divider, - container_fg, - .. - } = self; - - let prefix_lower = match prefix { - ContainerType::Background => "background", - ContainerType::Primary => "primary", - ContainerType::Secondary => "secondary", - }; - let component = widget_gtk4_css(prefix_lower, container_component); - - format!( - r#" -@define-color {prefix_lower}_container #{{{container}}}; -@define-color {prefix_lower}_container_divider #{{{container_divider}}}; -@define-color {prefix_lower}_container_fg #{{{container_fg}}}; -{component} -"# - ) - } -} - -impl AsGtk4Css for Accent -where - C: Clone + fmt::Debug + Default + Into + From + Serialize + DeserializeOwned, -{ - fn as_css(&self) -> String { - let Accent { - accent, - accent_fg, - accent_nav_handle_fg, - suggested, - } = self; - let suggested = widget_gtk4_css("suggested", suggested); - - format!( - r#" -@define-color accent #{{{accent}}}; -@define-color accent_fg #{{{accent_fg}}}; -@define-color accent_nav_handle_fg #{{{accent_nav_handle_fg}}}; -{suggested} -"# - ) - } -} - -impl AsGtk4Css for Destructive -where - C: Clone + fmt::Debug + Default + Into + From + Serialize + DeserializeOwned, -{ - fn as_css(&self) -> String { - let Destructive { destructive } = &self; - widget_gtk4_css("destructive", destructive) - } -} - -fn widget_gtk4_css( - prefix: &str, - Widget { - base, - hover, - pressed, - focused, - divider, - text, - text_opacity_80, - disabled, - disabled_fg, - }: &Widget, -) -> String { +fn component_gtk4_css(prefix: &str, c: &Component) -> String { format!( r#" -@define-color {prefix}_widget_base #{{{base}}}; -@define-color {prefix}_widget_hover #{{{hover}}}; -@define-color {prefix}_widget_pressed #{{{pressed}}}; -@define-color {prefix}_widget_focused #{{{focused}}}; -@define-color {prefix}_widget_divider #{{{divider}}}; -@define-color {prefix}_widget_fg #{{{text}}}; -@define-color {prefix}_widget_fg_opacity_80 #{{{text_opacity_80}}}; -@define-color {prefix}_widget_disabled #{{{disabled}}}; -@define-color {prefix}_widget_disabled_fg #{{{disabled_fg}}}; -"# +@define-color {prefix}_color #{}; +@define-color {prefix}_bg_color #{}; +@define-color {prefix}_fg_color #{}; +"#, + to_hex(c.base), + to_hex(c.base), + to_hex(c.on), ) } + +fn to_hex(c: Srgba) -> String { + let c_u8: Rgba = c.into_format(); + format!("{:02x}{:02x}{:02x}", c_u8.red, c_u8.green, c_u8.blue) +} diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 31307629..d2ea7853 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -1,8 +1,3 @@ -#[cfg(feature = "gtk4-theme")] +#[cfg(feature = "gtk4-output")] /// Module for outputting the Cosmic gtk4 theme type as CSS pub mod gtk4_output; -#[cfg(feature = "gtk4-theme")] -pub use gtk4_output::*; - -#[cfg(feature = "ron-serialization")] -pub use ron::*;