Add color scheme for qt apps
This commit is contained in:
parent
e568122083
commit
4fb4542d69
3 changed files with 329 additions and 0 deletions
|
|
@ -690,6 +690,9 @@ impl Theme {
|
|||
let config =
|
||||
Config::new(Self::id(), Self::VERSION).map_err(|e| (vec![e], Self::default()))?;
|
||||
let is_dark = ThemeMode::is_dark(&config).map_err(|e| (vec![e], Self::default()))?;
|
||||
Self::get_active_with_brightness(is_dark)
|
||||
}
|
||||
pub fn get_active_with_brightness(is_dark: bool) -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
||||
let config = if is_dark {
|
||||
Self::dark_config()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ use crate::Theme;
|
|||
/// Module for outputting the Cosmic gtk4 theme type as CSS
|
||||
pub mod gtk4_output;
|
||||
|
||||
/// Module for outputting the Cosmic qt theme type as kdeglobals
|
||||
pub mod qt_output;
|
||||
|
||||
pub mod vs_code;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
|
@ -22,8 +25,10 @@ impl Theme {
|
|||
#[inline]
|
||||
pub fn apply_exports(&self) -> Result<(), OutputError> {
|
||||
let gtk_res = Theme::apply_gtk(self.is_dark);
|
||||
let qt_res = Theme::apply_qt(self.is_dark);
|
||||
let vs_res = self.clone().apply_vs_code();
|
||||
gtk_res?;
|
||||
qt_res?;
|
||||
vs_res?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -31,15 +36,19 @@ impl Theme {
|
|||
#[inline]
|
||||
pub fn write_exports(&self) -> Result<(), OutputError> {
|
||||
let gtk_res = self.write_gtk4();
|
||||
let qt_res = self.write_qt();
|
||||
gtk_res?;
|
||||
qt_res?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset_exports() -> Result<(), OutputError> {
|
||||
let gtk_res = Theme::reset_gtk();
|
||||
let qt_res = Theme::reset_qt();
|
||||
let vs_res = Theme::reset_vs_code();
|
||||
gtk_res?;
|
||||
qt_res?;
|
||||
vs_res?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
317
cosmic-theme/src/output/qt_output.rs
Normal file
317
cosmic-theme/src/output/qt_output.rs
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
use crate::{Component, Theme, composite::over, steps::steps};
|
||||
use palette::{Darken, IntoColor, Lighten, Mix, Srgba, WithAlpha, rgb::Rgba};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{self, Write},
|
||||
num::NonZeroUsize,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use super::OutputError;
|
||||
|
||||
impl Theme {
|
||||
#[must_use]
|
||||
#[cold]
|
||||
/// Some high-level documentation for this file can be found at:
|
||||
/// https://web.archive.org/web/20250402234329/https://docs.kde.org/stable5/en/plasma-workspace/kcontrol/colors/
|
||||
pub fn as_qt_color_scheme(&self) -> String {
|
||||
let mut output = "# GENERATED BY COSMIC\n".to_string();
|
||||
|
||||
// Usually, disabled elements will have strongly reduced contrast and are often notably darker or lighter
|
||||
let disabled_color_effects = IniColorEffects {
|
||||
color: self.button.disabled,
|
||||
color_amount: 0.0,
|
||||
color_effect: ColorEffect::Desaturate,
|
||||
contrast_amount: 0.65,
|
||||
contrast_effect: ColorEffect::Fade,
|
||||
intensity_amount: 0.1,
|
||||
intensity_effect: IntensityEffect::Lighten, // TODO: different for light mode?
|
||||
};
|
||||
// Usually, inactive elements will have reduced contrast (text fades slightly into the background) and may have slightly reduced intensity
|
||||
let inactive_color_effects = IniColorEffects {
|
||||
color: self.palette.gray_1,
|
||||
color_amount: 0.025,
|
||||
color_effect: ColorEffect::Tint,
|
||||
contrast_amount: 0.1,
|
||||
contrast_effect: ColorEffect::Tint,
|
||||
intensity_amount: 0.0,
|
||||
intensity_effect: IntensityEffect::Shade,
|
||||
};
|
||||
|
||||
// window elements that are not buttons or views
|
||||
let window_colors = {
|
||||
let window = &self.background.component;
|
||||
IniColors {
|
||||
background_alternate: window.base.mix(self.accent.base, 0.1),
|
||||
background_normal: window.base,
|
||||
foreground_active: self.accent_text_color(),
|
||||
foreground_inactive: self.background.component.on.mix(window.base, 0.1),
|
||||
foreground_link: self.link_button.base,
|
||||
foreground_negative: self.destructive_text_color(),
|
||||
foreground_neutral: self.warning_text_color(),
|
||||
foreground_normal: self.background.component.on,
|
||||
foreground_positive: self.success_text_color(),
|
||||
foreground_visited: self.accent_text_color(),
|
||||
}
|
||||
};
|
||||
let view_colors = IniColors {
|
||||
background_alternate: self.background.base.mix(self.accent.base, 0.1),
|
||||
background_normal: self.background.base,
|
||||
..window_colors
|
||||
};
|
||||
|
||||
// selected text and items
|
||||
let selection_colors = {
|
||||
let selected = self.background.component.selected;
|
||||
let selected_text = self.background.component.selected_text;
|
||||
IniColors {
|
||||
background_alternate: selected.mix(self.background.base, 0.5),
|
||||
background_normal: selected,
|
||||
// TODO: DecorationFocus and DecorationHover should = background_normal not foreground_active here
|
||||
foreground_active: selected_text,
|
||||
foreground_inactive: selected_text.mix(selected, 0.5),
|
||||
foreground_link: self.link_button.on,
|
||||
foreground_negative: self.destructive_color(),
|
||||
foreground_neutral: self.warning_color(),
|
||||
foreground_normal: selected_text,
|
||||
foreground_positive: self.success_color(),
|
||||
foreground_visited: self.accent_color(),
|
||||
}
|
||||
};
|
||||
|
||||
let button_colors = IniColors {
|
||||
background_alternate: self.accent_button.base,
|
||||
background_normal: self.button.base,
|
||||
..window_colors
|
||||
};
|
||||
|
||||
// Complementary: Areas of applications with an alternative color scheme; usually with a dark background for light color schemes.
|
||||
let complementary_colors = {
|
||||
let dark = if self.is_dark {
|
||||
self.clone()
|
||||
} else {
|
||||
Self::get_active_with_brightness(false).unwrap_or_else(|_| self.clone())
|
||||
};
|
||||
IniColors {
|
||||
background_alternate: dark.accent.base,
|
||||
background_normal: dark.background.base,
|
||||
foreground_active: dark.accent_text_color(),
|
||||
foreground_inactive: dark.background.on.mix(dark.background.base, 0.1),
|
||||
foreground_link: dark.link_button.base,
|
||||
foreground_negative: dark.destructive_text_color(),
|
||||
foreground_neutral: dark.warning_text_color(),
|
||||
foreground_normal: dark.background.on,
|
||||
foreground_positive: dark.success_text_color(),
|
||||
foreground_visited: dark.accent_text_color(),
|
||||
}
|
||||
};
|
||||
|
||||
let header_colors = IniColors {
|
||||
background_alternate: window_colors.background_normal,
|
||||
background_normal: window_colors.background_alternate,
|
||||
..window_colors
|
||||
};
|
||||
let header_colors_inactive = &window_colors;
|
||||
// tool tips, "What's This" tips, and similar elements
|
||||
let tooltip_colors = &header_colors;
|
||||
|
||||
output.push_str(&format!(
|
||||
r#"
|
||||
[ColorEffects:Disabled]
|
||||
{}
|
||||
|
||||
[ColorEffects:Inactive]
|
||||
ChangeSelectionColor=false
|
||||
Enable=false
|
||||
{}
|
||||
|
||||
[Colors:Button]
|
||||
{}
|
||||
|
||||
[Colors:Complementary]
|
||||
{}
|
||||
|
||||
[Colors:Header]
|
||||
{}
|
||||
|
||||
[Colors:Header][Inactive]
|
||||
{}
|
||||
|
||||
[Colors:Selection]
|
||||
{}
|
||||
|
||||
[Colors:Tooltip]
|
||||
{}
|
||||
|
||||
[Colors:View]
|
||||
{}
|
||||
|
||||
[Colors:Window]
|
||||
{}
|
||||
|
||||
[General]
|
||||
ColorScheme=CosmicDark
|
||||
Name=COSMIC Dark
|
||||
shadeSortColumn=true
|
||||
|
||||
[KDE]
|
||||
contrast=4
|
||||
|
||||
[WM]
|
||||
activeBackground=39,44,49
|
||||
activeBlend=252,252,252
|
||||
activeForeground=252,252,252
|
||||
inactiveBackground=32,36,40
|
||||
inactiveBlend=161,169,177
|
||||
inactiveForeground=161,169,177
|
||||
"#,
|
||||
format_ini_color_effects(&disabled_color_effects),
|
||||
format_ini_color_effects(&inactive_color_effects),
|
||||
format_ini_colors(&button_colors),
|
||||
format_ini_colors(&complementary_colors),
|
||||
format_ini_colors(&header_colors),
|
||||
format_ini_colors(&header_colors_inactive),
|
||||
format_ini_colors(&selection_colors),
|
||||
format_ini_colors(&tooltip_colors),
|
||||
format_ini_colors(&view_colors),
|
||||
format_ini_colors(&window_colors),
|
||||
));
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a color in the form `r,g,b` e.g. `255,255,255`
|
||||
pub fn to_rgb(c: Srgba) -> String {
|
||||
let c_u8: Rgba<palette::encoding::Srgb, u8> = c.into_format();
|
||||
format!("{},{},{}", c_u8.red, c_u8.green, c_u8.blue)
|
||||
}
|
||||
|
||||
fn format_ini_color_effects(color_effects: &IniColorEffects) -> String {
|
||||
format!(
|
||||
r#"Color={}
|
||||
ColorAmount={}
|
||||
ColorEffect={}
|
||||
ContrastAmount={}
|
||||
ContrastEffect={}
|
||||
IntensityAmount={}
|
||||
IntensityEffect={}"#,
|
||||
to_rgb(color_effects.color),
|
||||
color_effects.color_amount,
|
||||
color_effects.color_effect.as_u8(),
|
||||
color_effects.contrast_amount,
|
||||
color_effects.contrast_effect.as_u8(),
|
||||
color_effects.intensity_amount,
|
||||
color_effects.intensity_effect.as_u8(),
|
||||
)
|
||||
}
|
||||
|
||||
fn format_ini_colors(colors: &IniColors) -> String {
|
||||
format!(
|
||||
r#"BackgroundAlternate={}
|
||||
BackgroundNormal={}
|
||||
DecorationFocus={}
|
||||
DecorationHover={}
|
||||
ForegroundActive={}
|
||||
ForegroundInactive={}
|
||||
ForegroundLink={}
|
||||
ForegroundNegative={}
|
||||
ForegroundNeutral={}
|
||||
ForegroundNormal={}
|
||||
ForegroundPositive={}
|
||||
ForegroundVisited={}"#,
|
||||
to_rgb(colors.background_alternate),
|
||||
to_rgb(colors.background_normal),
|
||||
// Decoration colors are always the same as foreground_active
|
||||
to_rgb(colors.foreground_active),
|
||||
to_rgb(colors.foreground_active),
|
||||
to_rgb(colors.foreground_active),
|
||||
to_rgb(colors.foreground_inactive),
|
||||
to_rgb(colors.foreground_link),
|
||||
to_rgb(colors.foreground_negative),
|
||||
to_rgb(colors.foreground_neutral),
|
||||
to_rgb(colors.foreground_normal),
|
||||
to_rgb(colors.foreground_positive),
|
||||
to_rgb(colors.foreground_visited),
|
||||
)
|
||||
}
|
||||
|
||||
struct IniColorEffects {
|
||||
color: Srgba,
|
||||
color_amount: f32,
|
||||
color_effect: ColorEffect,
|
||||
contrast_amount: f32,
|
||||
/// Applied to the text, using the background as the reference color.
|
||||
contrast_effect: ColorEffect,
|
||||
intensity_amount: f32,
|
||||
intensity_effect: IntensityEffect,
|
||||
}
|
||||
/// Each color set is made up of a number of roles which are available in all other sets.
|
||||
/// In addition, except for Inactive Text, there is a corresponding background role for each of the text roles. Currently (except for Normal and Alternate Background), these colors are not chosen here but are automatically determined based on Normal Background and the corresponding Text color.
|
||||
struct IniColors {
|
||||
/// used when there is a need to subtly change the background to aid in item association. This might be used e.g. as the background of a heading, but is mostly used for alternating rows in lists, especially multi-column lists, to aid in visually tracking rows.
|
||||
background_alternate: Srgba,
|
||||
/// Normal background
|
||||
background_normal: Srgba,
|
||||
/// used to indicate an active element or attract attention, e.g. alerts, notifications; also for hovered hyperlinks
|
||||
foreground_active: Srgba,
|
||||
/// used for text which should be unobtrusive, e.g. comments, "subtitles", unimportant information, etc.
|
||||
foreground_inactive: Srgba,
|
||||
/// used for hyperlinks or to otherwise indicate "something which may be visited", or to show relationships
|
||||
foreground_link: Srgba,
|
||||
/// used for errors, failure notices, notifications that an action may be dangerous (e.g. unsafe web page or security context), etc.
|
||||
foreground_negative: Srgba,
|
||||
/// used to draw attention when another role is not appropriate; e.g. warnings, to indicate secure/encrypted content, etc.
|
||||
foreground_neutral: Srgba,
|
||||
/// Normal foreground
|
||||
foreground_normal: Srgba,
|
||||
/// used for success notices, to indicate trusted content, etc.
|
||||
foreground_positive: Srgba,
|
||||
/// used for "something (e.g. a hyperlink) that has been visited", or to indicate something that is "old".
|
||||
foreground_visited: Srgba,
|
||||
}
|
||||
|
||||
/// Intensity allows the overall color to be lightened or darkened.
|
||||
enum IntensityEffect {
|
||||
/// Makes everything lighter or darker in a controlled manner.
|
||||
///
|
||||
/// intensity_amount increases or decreases the overall intensity (i.e. perceived brightness) by an absolute amount.
|
||||
Shade,
|
||||
/// Changes the intensity to a percentage of the initial value.
|
||||
Darken,
|
||||
/// Conceptually the opposite of darken; lighten can be thought of as working with "distance from white", where darken works with "distance from black".
|
||||
Lighten,
|
||||
}
|
||||
|
||||
impl IntensityEffect {
|
||||
pub fn as_u8(&self) -> u8 {
|
||||
match self {
|
||||
Self::Shade => 0,
|
||||
Self::Darken => 1,
|
||||
Self::Lighten => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This also changes the overall color like [IntensityEffect],
|
||||
/// but is not limited to intensity.
|
||||
enum ColorEffect {
|
||||
/// changes the relative chroma
|
||||
///
|
||||
/// This is available for "ColorEffect" but not "ContrastEffect".
|
||||
Desaturate,
|
||||
/// smoothly blends the original color into a reference color
|
||||
Fade,
|
||||
/// similar to Fade, except that the color (hue and chroma) changes more quickly while the intensity changes more slowly as the amount is increased
|
||||
Tint,
|
||||
}
|
||||
|
||||
impl ColorEffect {
|
||||
pub fn as_u8(&self) -> u8 {
|
||||
match self {
|
||||
Self::Desaturate => 0,
|
||||
Self::Fade => 1,
|
||||
Self::Tint => 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue