2024-02-19 17:33:09 -05:00
|
|
|
use crate::{composite::over, steps::steps, Component, Theme};
|
|
|
|
|
use palette::{rgb::Rgba, Darken, IntoColor, Lighten, Srgba};
|
2024-03-18 13:42:06 -04:00
|
|
|
use std::{
|
|
|
|
|
fs::{self, File},
|
|
|
|
|
io::Write,
|
|
|
|
|
num::NonZeroUsize,
|
|
|
|
|
};
|
2024-01-30 19:20:35 -05:00
|
|
|
use thiserror::Error;
|
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
pub enum OutputError {
|
|
|
|
|
#[error("IO Error: {0}")]
|
|
|
|
|
Io(std::io::Error),
|
|
|
|
|
#[error("Missing config directory")]
|
|
|
|
|
MissingConfigDir,
|
2023-05-30 12:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
impl Theme {
|
2024-03-18 13:42:06 -04:00
|
|
|
#[must_use]
|
2024-01-30 19:20:35 -05:00
|
|
|
/// turn the theme into css
|
|
|
|
|
pub fn as_gtk4(&self) -> String {
|
2023-05-30 12:03:15 -04:00
|
|
|
let Self {
|
|
|
|
|
background,
|
|
|
|
|
primary,
|
|
|
|
|
secondary,
|
|
|
|
|
accent,
|
|
|
|
|
destructive,
|
2024-01-30 19:20:35 -05:00
|
|
|
warning,
|
|
|
|
|
success,
|
2024-02-19 17:33:09 -05:00
|
|
|
palette,
|
2023-05-30 12:03:15 -04:00
|
|
|
..
|
|
|
|
|
} = self;
|
|
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let window_bg = to_hex(background.base);
|
|
|
|
|
let window_fg = to_hex(background.on);
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
let headerbar_backdrop = to_hex(background.base);
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let card_bg = to_hex(background.component.base);
|
|
|
|
|
let card_fg = to_hex(background.component.on);
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let thumbnail_bg = to_hex(background.component.base);
|
|
|
|
|
let thumbnail_fg = to_hex(background.component.on);
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let dialog_bg = to_hex(primary.base);
|
|
|
|
|
let dialog_fg = to_hex(primary.on);
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let popover_bg = to_hex(background.component.base);
|
|
|
|
|
let popover_fg = to_hex(background.component.on);
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let shade = to_hex(if self.is_dark {
|
|
|
|
|
Rgba::new(0.0, 0.0, 0.0, 0.32)
|
2023-05-30 12:03:15 -04:00
|
|
|
} else {
|
2024-01-30 19:20:35 -05:00
|
|
|
Rgba::new(0.0, 0.0, 0.0, 0.08)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let mut inverted_bg_divider = background.base;
|
|
|
|
|
inverted_bg_divider.alpha = 0.5;
|
|
|
|
|
let scrollbar_outline = to_hex(inverted_bg_divider);
|
|
|
|
|
|
|
|
|
|
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};
|
2024-02-19 17:33:09 -05:00
|
|
|
"#};
|
2024-01-30 19:20:35 -05:00
|
|
|
|
|
|
|
|
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));
|
2023-05-30 12:03:15 -04:00
|
|
|
|
2024-02-19 17:33:09 -05:00
|
|
|
css.push_str(&color_css("blue", palette.blue));
|
|
|
|
|
css.push_str(&color_css("green", palette.green));
|
|
|
|
|
css.push_str(&color_css("yellow", palette.yellow));
|
|
|
|
|
css.push_str(&color_css("red", palette.red));
|
|
|
|
|
css.push_str(&color_css("orange", palette.ext_orange));
|
|
|
|
|
css.push_str(&color_css("purple", palette.ext_purple));
|
|
|
|
|
let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap());
|
|
|
|
|
for (i, c) in neutral_steps[..5].iter().enumerate() {
|
|
|
|
|
css.push_str(&format!("@define-color light_{i} {};\n", to_hex(*c)));
|
|
|
|
|
}
|
|
|
|
|
for (i, c) in neutral_steps[5..].iter().enumerate() {
|
|
|
|
|
css.push_str(&format!("@define-color dark_{i} {};\n", to_hex(*c)));
|
|
|
|
|
}
|
2024-01-30 19:20:35 -05:00
|
|
|
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 name = if self.is_dark {
|
|
|
|
|
"dark.css"
|
2023-05-30 12:03:15 -04:00
|
|
|
} else {
|
2024-01-30 19:20:35 -05:00
|
|
|
"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)?;
|
2023-05-30 12:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
let mut file = File::create(config_dir.join(name)).map_err(OutputError::Io)?;
|
|
|
|
|
file.write_all(css_str.as_bytes())
|
|
|
|
|
.map_err(OutputError::Io)?;
|
|
|
|
|
|
2023-05-30 12:03:15 -04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-18 13:42:06 -04:00
|
|
|
/// Apply gtk color variable settings
|
|
|
|
|
pub fn apply_gtk(&self) -> Result<(), OutputError> {
|
|
|
|
|
let Some(config_dir) = dirs::config_dir() else {
|
|
|
|
|
return Err(OutputError::MissingConfigDir);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let gtk4 = config_dir.join("gtk-4.0");
|
|
|
|
|
let gtk3 = config_dir.join("gtk-3.0");
|
|
|
|
|
|
|
|
|
|
fs::create_dir_all(>k4).map_err(OutputError::Io)?;
|
|
|
|
|
fs::create_dir_all(>k3).map_err(OutputError::Io)?;
|
|
|
|
|
|
|
|
|
|
let gtk4_dest = gtk4.join("gtk.css");
|
|
|
|
|
|
|
|
|
|
fs::rename(>k4_dest, gtk4.join("gtk.css.bak")).map_err(OutputError::Io)?;
|
|
|
|
|
|
|
|
|
|
fs::copy(
|
|
|
|
|
gtk4.join("cosmic").join(if self.is_dark {
|
|
|
|
|
"dark.css"
|
|
|
|
|
} else {
|
|
|
|
|
"light.css"
|
|
|
|
|
}),
|
|
|
|
|
>k4_dest,
|
|
|
|
|
)
|
|
|
|
|
.map_err(OutputError::Io)?;
|
|
|
|
|
|
|
|
|
|
#[cfg(target_family = "unix")]
|
|
|
|
|
{
|
|
|
|
|
use std::fs::metadata;
|
|
|
|
|
use std::os::unix::fs::symlink;
|
|
|
|
|
|
|
|
|
|
let gtk3_dest = gtk3.join("gtk.css");
|
|
|
|
|
let gtk3_dest_bak = gtk3.join("gtk.css.bak");
|
|
|
|
|
|
|
|
|
|
if gtk3_dest.exists() {
|
|
|
|
|
if metadata(>k3_dest)
|
|
|
|
|
.map_err(OutputError::Io)?
|
|
|
|
|
.file_type()
|
|
|
|
|
.is_symlink()
|
|
|
|
|
{
|
|
|
|
|
fs::remove_file(>k3_dest).map_err(OutputError::Io)?;
|
|
|
|
|
} else {
|
|
|
|
|
fs::rename(>k3_dest, gtk3_dest_bak).map_err(OutputError::Io)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
symlink(gtk4_dest, gtk3_dest).map_err(OutputError::Io)?;
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
2023-05-30 12:03:15 -04:00
|
|
|
}
|
|
|
|
|
|
2024-01-30 19:20:35 -05:00
|
|
|
fn component_gtk4_css(prefix: &str, c: &Component) -> String {
|
2023-05-30 12:03:15 -04:00
|
|
|
format!(
|
|
|
|
|
r#"
|
2024-01-30 19:20:35 -05:00
|
|
|
@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),
|
2023-05-30 12:03:15 -04:00
|
|
|
)
|
|
|
|
|
}
|
2024-01-30 19:20:35 -05:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
2024-02-19 17:33:09 -05:00
|
|
|
|
|
|
|
|
fn color_css(prefix: &str, c_3: Srgba) -> String {
|
|
|
|
|
let oklch: palette::Oklch = c_3.into_color();
|
|
|
|
|
let c_2: Srgba = oklch.lighten(0.1).into_color();
|
|
|
|
|
let c_1: Srgba = oklch.lighten(0.2).into_color();
|
|
|
|
|
let c_4: Srgba = oklch.darken(0.1).into_color();
|
|
|
|
|
let c_5: Srgba = oklch.darken(0.2).into_color();
|
|
|
|
|
let c_1 = to_hex(c_1);
|
|
|
|
|
let c_2 = to_hex(c_2);
|
|
|
|
|
let c_3 = to_hex(c_3);
|
|
|
|
|
let c_4 = to_hex(c_4);
|
|
|
|
|
let c_5 = to_hex(c_5);
|
|
|
|
|
|
|
|
|
|
format! {r#"
|
|
|
|
|
@define-color {prefix}_1 #{c_1};
|
|
|
|
|
@define-color {prefix}_2 #{c_2};
|
|
|
|
|
@define-color {prefix}_3 #{c_3};
|
|
|
|
|
@define-color {prefix}_4 #{c_4};
|
|
|
|
|
@define-color {prefix}_5 #{c_5};
|
|
|
|
|
"#}
|
|
|
|
|
}
|