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.
This commit is contained in:
parent
4154428a63
commit
d6b87a0222
5 changed files with 175 additions and 160 deletions
|
|
@ -150,6 +150,10 @@ members = [
|
||||||
]
|
]
|
||||||
exclude = ["examples/design-demo", "iced"]
|
exclude = ["examples/design-demo", "iced"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
dirs = "5.0.1"
|
||||||
|
|
||||||
|
|
||||||
[patch."https://github.com/pop-os/libcosmic"]
|
[patch."https://github.com/pop-os/libcosmic"]
|
||||||
libcosmic = { path = "./" }
|
libcosmic = { path = "./" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ subscription = ["iced_futures"]
|
||||||
zbus = { version = "3.14.1", default-features = false, optional = true }
|
zbus = { version = "3.14.1", default-features = false, optional = true }
|
||||||
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
||||||
calloop = { version = "0.12.2", optional = true }
|
calloop = { version = "0.12.2", optional = true }
|
||||||
dirs = "5.0.1"
|
|
||||||
notify = "6.0.0"
|
notify = "6.0.0"
|
||||||
ron = "0.8.0"
|
ron = "0.8.0"
|
||||||
serde = "1.0.152"
|
serde = "1.0.152"
|
||||||
|
|
@ -23,6 +22,7 @@ iced_futures = { path = "../iced/futures/", default-features = false, optional =
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", branch = "cosmic-settings-daemon", optional = true }
|
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 }
|
futures-util = { version = "0.3", optional = true }
|
||||||
|
dirs.workspace = true
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
xdg = "2.1"
|
xdg = "2.1"
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
gtk4-output = []
|
||||||
no-default = []
|
no-default = []
|
||||||
theme-from-image = ["kmeans_colors", "image"]
|
theme-from-image = ["kmeans_colors", "image"]
|
||||||
|
|
||||||
|
|
@ -24,3 +25,5 @@ ron = "0.8"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
csscolorparser = {version = "0.6.2", features = ["serde"]}
|
csscolorparser = {version = "0.6.2", features = ["serde"]}
|
||||||
cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription", "macro"] }
|
cosmic-config = { path = "../cosmic-config/", default-features = false, features = ["subscription", "macro"] }
|
||||||
|
dirs.workspace = true
|
||||||
|
thiserror = "1.0.5"
|
||||||
|
|
|
||||||
|
|
@ -1,187 +1,200 @@
|
||||||
use crate::{
|
use crate::{composite::over, Component, Theme};
|
||||||
model::{Accent, Container, ContainerType, Destructive, Widget},
|
use palette::{rgb::Rgba, Srgba};
|
||||||
Hex, Theme, NAME,
|
use std::{fs::File, io::prelude::*};
|
||||||
};
|
use thiserror::Error;
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use palette::Srgba;
|
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
|
||||||
use std::{fmt, fs::File, io::prelude::*, path::PathBuf};
|
|
||||||
|
|
||||||
pub(crate) const CSS_DIR: &'static str = "css";
|
#[derive(Error, Debug)]
|
||||||
pub(crate) const THEME_DIR: &'static str = "themes";
|
pub enum OutputError {
|
||||||
|
#[error("IO Error: {0}")]
|
||||||
/// Trait for outputting the Theme variables as Gtk4CSS
|
Io(std::io::Error),
|
||||||
pub trait Gtk4Output {
|
#[error("Missing config directory")]
|
||||||
/// turn the theme into css
|
MissingConfigDir,
|
||||||
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<()>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> Gtk4Output for Theme<C>
|
impl Theme {
|
||||||
where
|
/// turn the theme into css
|
||||||
C: Clone
|
pub fn as_gtk4(&self) -> String {
|
||||||
+ fmt::Debug
|
|
||||||
+ Default
|
|
||||||
+ Into<Hex>
|
|
||||||
+ Into<Srgba>
|
|
||||||
+ From<Srgba>
|
|
||||||
+ Serialize
|
|
||||||
+ DeserializeOwned,
|
|
||||||
{
|
|
||||||
fn as_css(&self) -> String {
|
|
||||||
let Self {
|
let Self {
|
||||||
background,
|
background,
|
||||||
primary,
|
primary,
|
||||||
secondary,
|
secondary,
|
||||||
accent,
|
accent,
|
||||||
destructive,
|
destructive,
|
||||||
|
warning,
|
||||||
|
success,
|
||||||
..
|
..
|
||||||
} = self;
|
} = self;
|
||||||
let mut css = String::new();
|
|
||||||
|
|
||||||
css.push_str(&background.as_css());
|
let window_bg = to_hex(background.base);
|
||||||
css.push_str(&primary.as_css());
|
let window_fg = to_hex(background.on);
|
||||||
css.push_str(&secondary.as_css());
|
|
||||||
css.push_str(&accent.as_css());
|
let view_bg = to_hex(primary.base);
|
||||||
css.push_str(&destructive.as_css());
|
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
|
css
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&self) -> Result<()> {
|
/// write the CSS to the appropriate directory
|
||||||
// TODO sass -> css
|
/// Should be written in the XDG config directory for gtk-4.0
|
||||||
let ron_str = ron::ser::to_string_pretty(self, Default::default())?;
|
///
|
||||||
let css_str = self.as_css();
|
/// # 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 name = if self.is_dark {
|
||||||
let css_path: PathBuf = [NAME, CSS_DIR].iter().collect();
|
"dark.css"
|
||||||
|
|
||||||
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())?;
|
|
||||||
} else {
|
} 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 file = File::create(config_dir.join(name)).map_err(OutputError::Io)?;
|
||||||
let mut f = File::create(p)?;
|
file.write_all(css_str.as_bytes())
|
||||||
f.write_all(css_str.as_bytes())?;
|
.map_err(OutputError::Io)?;
|
||||||
} else {
|
|
||||||
bail!("Failed to write RON theme.")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for converting theme data into gtk4 CSS
|
/// Trait for converting theme data into gtk4 CSS
|
||||||
pub trait AsGtk4Css<C>
|
pub trait AsGtk4Css {
|
||||||
where
|
|
||||||
C: Copy + Into<Srgba> + From<Srgba>,
|
|
||||||
{
|
|
||||||
/// function for converting theme data into gtk4 CSS
|
/// function for converting theme data into gtk4 CSS
|
||||||
fn as_css(&self) -> String;
|
fn as_css(&self) -> String;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> AsGtk4Css<C> for Container<C>
|
fn component_gtk4_css(prefix: &str, c: &Component) -> String {
|
||||||
where
|
|
||||||
C: Copy + Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + 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<C> AsGtk4Css<C> for Accent<C>
|
|
||||||
where
|
|
||||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + 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<C> AsGtk4Css<C> for Destructive<C>
|
|
||||||
where
|
|
||||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
|
||||||
{
|
|
||||||
fn as_css(&self) -> String {
|
|
||||||
let Destructive { destructive } = &self;
|
|
||||||
widget_gtk4_css("destructive", destructive)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn widget_gtk4_css<C: fmt::Display>(
|
|
||||||
prefix: &str,
|
|
||||||
Widget {
|
|
||||||
base,
|
|
||||||
hover,
|
|
||||||
pressed,
|
|
||||||
focused,
|
|
||||||
divider,
|
|
||||||
text,
|
|
||||||
text_opacity_80,
|
|
||||||
disabled,
|
|
||||||
disabled_fg,
|
|
||||||
}: &Widget<C>,
|
|
||||||
) -> String {
|
|
||||||
format!(
|
format!(
|
||||||
r#"
|
r#"
|
||||||
@define-color {prefix}_widget_base #{{{base}}};
|
@define-color {prefix}_color #{};
|
||||||
@define-color {prefix}_widget_hover #{{{hover}}};
|
@define-color {prefix}_bg_color #{};
|
||||||
@define-color {prefix}_widget_pressed #{{{pressed}}};
|
@define-color {prefix}_fg_color #{};
|
||||||
@define-color {prefix}_widget_focused #{{{focused}}};
|
"#,
|
||||||
@define-color {prefix}_widget_divider #{{{divider}}};
|
to_hex(c.base),
|
||||||
@define-color {prefix}_widget_fg #{{{text}}};
|
to_hex(c.base),
|
||||||
@define-color {prefix}_widget_fg_opacity_80 #{{{text_opacity_80}}};
|
to_hex(c.on),
|
||||||
@define-color {prefix}_widget_disabled #{{{disabled}}};
|
|
||||||
@define-color {prefix}_widget_disabled_fg #{{{disabled_fg}}};
|
|
||||||
"#
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_hex(c: Srgba) -> String {
|
||||||
|
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
||||||
|
format!("{:02x}{:02x}{:02x}", c_u8.red, c_u8.green, c_u8.blue)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
#[cfg(feature = "gtk4-theme")]
|
#[cfg(feature = "gtk4-output")]
|
||||||
/// Module for outputting the Cosmic gtk4 theme type as CSS
|
/// Module for outputting the Cosmic gtk4 theme type as CSS
|
||||||
pub mod gtk4_output;
|
pub mod gtk4_output;
|
||||||
#[cfg(feature = "gtk4-theme")]
|
|
||||||
pub use gtk4_output::*;
|
|
||||||
|
|
||||||
#[cfg(feature = "ron-serialization")]
|
|
||||||
pub use ron::*;
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue