Cosmic advanced text (#103)
* wip: update to use cosmic-advanced-text * use cosmic-advanced-text branch of iced * fix: line height and spacing for segmented button and update to get svg fix * fix: spin button styling & spacing * update iced to fix segmented button border radius * feat: example improvements * feat: helper for loading fonts * feat: add focus style to button * fix: slider height and iced fixed * feat: hash icon width and height * cleanup * update ci * refactor: always use lazy feature of iced * update iced * update iced * cleanup & update iced * update iced: new slider & tiny-skia quad updates * update iced: fixes for tiny-skia quad rendering with edge case border radius * re-export iced_runtime & iced_widget * merge master * udpate iced * update iced * update iced * update iced * fix: make rectangle_tracker subscription only return update if there is some * feat: derive macro for loading a cosmic-config * feat (cosmic-config): iced subscription * fix (example): update to rectangle tracker subscription * fix (cosmic-config) * refactor(cosmic-config-derive): add support for types with generic parameters * fix (cosmic-config): feature gate updates for subscription helpers * feat: support for custom & system themes + move cosmic-theme to libcosmic * feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk * update iced * update and reexport sctk * fix: applet border radius * feat (cosmic-theme): add id and name methods * fix(cosmic-theme): reexport palette from cosmic-theme * fix(cosmic-config-derive): allow use with reexported cosmic-config * feat: update iced with fix and refactor applet env vars * update iced
This commit is contained in:
parent
a173794bed
commit
e056e8c830
65 changed files with 3431 additions and 405 deletions
26
cosmic-theme/src/model/constraint.rs
Normal file
26
cosmic-theme/src/model/constraint.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/// Cosmic theme custom constraints which are used to pick colors
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ThemeConstraints {
|
||||
/// requested contrast ratio for elevated surfaces
|
||||
pub elevated_contrast_ratio: f32,
|
||||
/// requested contrast ratio for dividers
|
||||
pub divider_contrast_ratio: f32,
|
||||
/// requested contrast ratio for text
|
||||
pub text_contrast_ratio: f32,
|
||||
/// gray scale or color for dividers
|
||||
pub divider_gray_scale: bool,
|
||||
/// elevated surfaces are lightened or darkened
|
||||
pub lighten: bool,
|
||||
}
|
||||
|
||||
impl Default for ThemeConstraints {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
elevated_contrast_ratio: 1.1,
|
||||
divider_contrast_ratio: 1.51,
|
||||
text_contrast_ratio: 7.0,
|
||||
divider_gray_scale: true,
|
||||
lighten: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
252
cosmic-theme/src/model/cosmic_palette.rs
Normal file
252
cosmic-theme/src/model/cosmic_palette.rs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
use std::{
|
||||
fmt,
|
||||
fs::File,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use directories::{BaseDirsExt, ProjectDirsExt};
|
||||
use lazy_static::lazy_static;
|
||||
use palette::Srgba;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
|
||||
use crate::{util::CssColor, NAME, PALETTE_DIR};
|
||||
|
||||
lazy_static! {
|
||||
/// built in light palette
|
||||
pub static ref LIGHT_PALETTE: CosmicPalette<CssColor> =
|
||||
ron::from_str(include_str!("light.ron")).unwrap();
|
||||
/// built in dark palette
|
||||
pub static ref DARK_PALETTE: CosmicPalette<CssColor> =
|
||||
ron::from_str(include_str!("dark.ron")).unwrap();
|
||||
}
|
||||
|
||||
/// Palette type
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum CosmicPalette<C> {
|
||||
/// Dark mode
|
||||
Dark(CosmicPaletteInner<C>),
|
||||
/// Light mode
|
||||
Light(CosmicPaletteInner<C>),
|
||||
/// High contrast light mode
|
||||
HighContrastLight(CosmicPaletteInner<C>),
|
||||
/// High contrast dark mode
|
||||
HighContrastDark(CosmicPaletteInner<C>),
|
||||
}
|
||||
|
||||
impl<C> AsRef<CosmicPaletteInner<C>> for CosmicPalette<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
fn as_ref(&self) -> &CosmicPaletteInner<C> {
|
||||
match self {
|
||||
CosmicPalette::Dark(p) => p,
|
||||
CosmicPalette::Light(p) => p,
|
||||
CosmicPalette::HighContrastLight(p) => p,
|
||||
CosmicPalette::HighContrastDark(p) => p,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> CosmicPalette<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
/// check if the palette is dark
|
||||
pub fn is_dark(&self) -> bool {
|
||||
match self {
|
||||
CosmicPalette::Dark(_) | CosmicPalette::HighContrastDark(_) => true,
|
||||
CosmicPalette::Light(_) | CosmicPalette::HighContrastLight(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// check if the palette is high_contrast
|
||||
pub fn is_high_contrast(&self) -> bool {
|
||||
match self {
|
||||
CosmicPalette::HighContrastLight(_) | CosmicPalette::HighContrastDark(_) => true,
|
||||
CosmicPalette::Light(_) | CosmicPalette::Dark(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Default for CosmicPalette<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
fn default() -> Self {
|
||||
CosmicPalette::Dark(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// The palette for Cosmic Theme, from which all color properties are derived
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct CosmicPaletteInner<C> {
|
||||
/// name of the palette
|
||||
pub name: String,
|
||||
|
||||
/// basic palette
|
||||
/// blue: colors used for various points of emphasis in the UI
|
||||
pub blue: C,
|
||||
/// red: colors used for various points of emphasis in the UI
|
||||
pub red: C,
|
||||
/// green: colors used for various points of emphasis in the UI
|
||||
pub green: C,
|
||||
/// yellow: colors used for various points of emphasis in the UI
|
||||
pub yellow: C,
|
||||
|
||||
/// surface grays
|
||||
/// colors used for three levels of surfaces in the UI
|
||||
pub gray_1: C,
|
||||
/// colors used for three levels of surfaces in the UI
|
||||
pub gray_2: C,
|
||||
/// colors used for three levels of surfaces in the UI
|
||||
pub gray_3: C,
|
||||
|
||||
/// System Neutrals
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_1: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_2: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_3: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_4: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_5: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_6: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_7: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_8: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_9: C,
|
||||
/// A wider spread of dark colors for more general use.
|
||||
pub neutral_10: C,
|
||||
|
||||
/// Extended Color Palette
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_warm_grey: C,
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_orange: C,
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_yellow: C,
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_blue: C,
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_purple: C,
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_pink: C,
|
||||
/// Colors used for themes, app icons, illustrations, and other brand purposes.
|
||||
pub ext_indigo: C,
|
||||
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_warm_grey: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_orange: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_yellow: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_purple: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_pink: C,
|
||||
/// Potential Accent Color Combos
|
||||
pub accent_indigo: C,
|
||||
}
|
||||
|
||||
impl From<CosmicPaletteInner<CssColor>> for CosmicPaletteInner<Srgba> {
|
||||
fn from(p: CosmicPaletteInner<CssColor>) -> Self {
|
||||
CosmicPaletteInner {
|
||||
name: p.name,
|
||||
blue: p.blue.into(),
|
||||
red: p.red.into(),
|
||||
green: p.green.into(),
|
||||
yellow: p.yellow.into(),
|
||||
gray_1: p.gray_1.into(),
|
||||
gray_2: p.gray_2.into(),
|
||||
gray_3: p.gray_3.into(),
|
||||
neutral_1: p.neutral_1.into(),
|
||||
neutral_2: p.neutral_2.into(),
|
||||
neutral_3: p.neutral_3.into(),
|
||||
neutral_4: p.neutral_4.into(),
|
||||
neutral_5: p.neutral_5.into(),
|
||||
neutral_6: p.neutral_6.into(),
|
||||
neutral_7: p.neutral_7.into(),
|
||||
neutral_8: p.neutral_8.into(),
|
||||
neutral_9: p.neutral_9.into(),
|
||||
neutral_10: p.neutral_10.into(),
|
||||
ext_warm_grey: p.ext_warm_grey.into(),
|
||||
ext_orange: p.ext_orange.into(),
|
||||
ext_yellow: p.ext_yellow.into(),
|
||||
ext_blue: p.ext_blue.into(),
|
||||
ext_purple: p.ext_purple.into(),
|
||||
ext_pink: p.ext_pink.into(),
|
||||
ext_indigo: p.ext_indigo.into(),
|
||||
accent_warm_grey: p.accent_warm_grey.into(),
|
||||
accent_orange: p.accent_orange.into(),
|
||||
accent_yellow: p.accent_yellow.into(),
|
||||
accent_purple: p.accent_purple.into(),
|
||||
accent_pink: p.accent_pink.into(),
|
||||
accent_indigo: p.accent_indigo.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> CosmicPalette<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
/// name of the palette
|
||||
pub fn name(&self) -> &str {
|
||||
match &self {
|
||||
CosmicPalette::Dark(p) => &p.name,
|
||||
CosmicPalette::Light(p) => &p.name,
|
||||
CosmicPalette::HighContrastLight(p) => &p.name,
|
||||
CosmicPalette::HighContrastDark(p) => &p.name,
|
||||
}
|
||||
}
|
||||
/// save the theme to the theme directory
|
||||
pub fn save(&self) -> anyhow::Result<()> {
|
||||
let ron_path: PathBuf = [NAME, PALETTE_DIR].iter().collect();
|
||||
let ron_dirs = directories::ProjectDirs::from_path(ron_path)
|
||||
.context("Failed to get project directories.")?;
|
||||
let ron_name = format!("{}.ron", self.name());
|
||||
|
||||
if let Ok(p) = ron_dirs.place_config_file(ron_name) {
|
||||
let mut f = File::create(p)?;
|
||||
f.write_all(ron::ser::to_string_pretty(self, Default::default())?.as_bytes())?;
|
||||
} else {
|
||||
anyhow::bail!("Failed to write RON theme.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// init the theme directory
|
||||
pub fn init() -> anyhow::Result<PathBuf> {
|
||||
let ron_path: PathBuf = [NAME, PALETTE_DIR].iter().collect();
|
||||
let base_dirs = directories::BaseDirs::new().context("Failed to get base directories.")?;
|
||||
Ok(base_dirs.create_config_directory(ron_path)?)
|
||||
}
|
||||
|
||||
/// load a theme by name
|
||||
pub fn load_from_name(name: &str) -> anyhow::Result<Self> {
|
||||
let ron_path: PathBuf = [NAME, PALETTE_DIR].iter().collect();
|
||||
let ron_dirs = directories::ProjectDirs::from_path(ron_path)
|
||||
.context("Failed to get project directories.")?;
|
||||
|
||||
let ron_name = format!("{}.ron", name);
|
||||
if let Some(p) = ron_dirs.find_config_file(ron_name) {
|
||||
let f = File::open(p)?;
|
||||
Ok(ron::de::from_reader(f)?)
|
||||
} else {
|
||||
anyhow::bail!("Failed to write RON theme.");
|
||||
}
|
||||
}
|
||||
|
||||
/// load a theme by path
|
||||
pub fn load(p: &dyn AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let f = File::open(p)?;
|
||||
Ok(ron::de::from_reader(f)?)
|
||||
}
|
||||
}
|
||||
95
cosmic-theme/src/model/dark.ron
Normal file
95
cosmic-theme/src/model/dark.ron
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
Dark (
|
||||
(
|
||||
name: "cosmic-dark",
|
||||
blue: (
|
||||
c: "#94EBEB",
|
||||
),
|
||||
red: (
|
||||
c: "#FFB5B5",
|
||||
),
|
||||
green: (
|
||||
c: "#ACF7D2",
|
||||
),
|
||||
yellow: (
|
||||
c: "#FFF19E",
|
||||
),
|
||||
gray_1: (
|
||||
c: "#1E1E1E",
|
||||
),
|
||||
gray_2: (
|
||||
c: "#292929",
|
||||
),
|
||||
gray_3: (
|
||||
c: "#2E2E2E",
|
||||
),
|
||||
neutral_1: (
|
||||
c: "#000000",
|
||||
),
|
||||
neutral_2: (
|
||||
c: "#272727",
|
||||
),
|
||||
neutral_3: (
|
||||
c: "#424242",
|
||||
),
|
||||
neutral_4: (
|
||||
c: "#5D5D5D",
|
||||
),
|
||||
neutral_5: (
|
||||
c: "#787878",
|
||||
),
|
||||
neutral_6: (
|
||||
c: "#939393",
|
||||
),
|
||||
neutral_7: (
|
||||
c: "#AEAEAE",
|
||||
),
|
||||
neutral_8: (
|
||||
c: "#C9C9C9",
|
||||
),
|
||||
neutral_9: (
|
||||
c: "#E4E4E4",
|
||||
),
|
||||
neutral_10: (
|
||||
c: "#FFFFFF",
|
||||
),
|
||||
ext_warm_grey: (
|
||||
c: "#9B8E8A",
|
||||
),
|
||||
ext_orange: (
|
||||
c: "#FFAD00",
|
||||
),
|
||||
ext_yellow: (
|
||||
c: "#FEDB40",
|
||||
),
|
||||
ext_blue: (
|
||||
c: "#48B9C7",
|
||||
),
|
||||
ext_purple: (
|
||||
c: "#CF7DFF",
|
||||
),
|
||||
ext_pink: (
|
||||
c: "#F93A83",
|
||||
),
|
||||
ext_indigo: (
|
||||
c: "#3E88FF",
|
||||
),
|
||||
accent_warm_grey: (
|
||||
c: "#554742",
|
||||
),
|
||||
accent_orange: (
|
||||
c: "#AF5C02",
|
||||
),
|
||||
accent_yellow: (
|
||||
c: "#966800",
|
||||
),
|
||||
accent_purple: (
|
||||
c: "#813FFF",
|
||||
),
|
||||
accent_pink: (
|
||||
c: "#F93A83",
|
||||
),
|
||||
accent_indigo: (
|
||||
c: "#3E88FF",
|
||||
),
|
||||
)
|
||||
)
|
||||
480
cosmic-theme/src/model/derivation.rs
Normal file
480
cosmic-theme/src/model/derivation.rs
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
use palette::Srgba;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
use crate::{util::over, CosmicPalette};
|
||||
|
||||
/// Theme Container colors of a theme, can be a theme background container, primary container, or secondary container
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Container<C> {
|
||||
/// the color of the container
|
||||
pub base: C,
|
||||
/// the color of components in the container
|
||||
pub component: Component<C>,
|
||||
/// the color of dividers in the container
|
||||
pub divider: C,
|
||||
/// the color of text in the container
|
||||
pub on: C,
|
||||
}
|
||||
|
||||
impl<C> Container<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
/// convert to srgba
|
||||
pub fn into_srgba(self) -> Container<Srgba> {
|
||||
Container {
|
||||
base: self.base.into(),
|
||||
component: self.component.into_srgba(),
|
||||
divider: self.divider.into(),
|
||||
on: self.on.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
palette: CosmicPalette<C>,
|
||||
container_type: ComponentType,
|
||||
bg: C,
|
||||
on_bg: C,
|
||||
) -> Self {
|
||||
let mut divider_c: Srgba = on_bg.clone().into();
|
||||
divider_c.alpha = 0.2;
|
||||
|
||||
let divider = over(divider_c.clone(), bg.clone());
|
||||
Self {
|
||||
base: bg,
|
||||
component: (palette, container_type).into(),
|
||||
divider: divider.into(),
|
||||
on: on_bg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> From<(CosmicPalette<C>, ContainerType)> for Container<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
fn from((p, t): (CosmicPalette<C>, ContainerType)) -> Self {
|
||||
match (p, t) {
|
||||
(CosmicPalette::Dark(p), ContainerType::Background) => Self::new(
|
||||
CosmicPalette::Dark(p.clone()),
|
||||
ComponentType::Background,
|
||||
p.gray_1.clone(),
|
||||
p.neutral_7.clone(),
|
||||
),
|
||||
(CosmicPalette::Dark(p), ContainerType::Primary) => Self::new(
|
||||
CosmicPalette::Dark(p.clone()),
|
||||
ComponentType::Primary,
|
||||
p.gray_2.clone(),
|
||||
p.neutral_8.clone(),
|
||||
),
|
||||
(CosmicPalette::Dark(p), ContainerType::Secondary) => Self::new(
|
||||
CosmicPalette::Dark(p.clone()),
|
||||
ComponentType::Secondary,
|
||||
p.gray_3.clone(),
|
||||
p.neutral_8.clone(),
|
||||
),
|
||||
(CosmicPalette::HighContrastDark(p), ContainerType::Background) => Self::new(
|
||||
CosmicPalette::HighContrastDark(p.clone()),
|
||||
ComponentType::Background,
|
||||
p.gray_1.clone(),
|
||||
p.neutral_8.clone(),
|
||||
),
|
||||
(CosmicPalette::HighContrastDark(p), ContainerType::Primary) => Self::new(
|
||||
CosmicPalette::HighContrastDark(p.clone()),
|
||||
ComponentType::Primary,
|
||||
p.gray_2.clone(),
|
||||
p.neutral_9.clone(),
|
||||
),
|
||||
(CosmicPalette::HighContrastDark(p), ContainerType::Secondary) => Self::new(
|
||||
CosmicPalette::HighContrastDark(p.clone()),
|
||||
ComponentType::Secondary,
|
||||
p.gray_3.clone(),
|
||||
p.neutral_9.clone(),
|
||||
),
|
||||
(CosmicPalette::Light(p), ContainerType::Background) => Self::new(
|
||||
CosmicPalette::Light(p.clone()),
|
||||
ComponentType::Background,
|
||||
p.gray_1.clone(),
|
||||
p.neutral_9.clone(),
|
||||
),
|
||||
(CosmicPalette::Light(p), ContainerType::Primary) => Self::new(
|
||||
CosmicPalette::Light(p.clone()),
|
||||
ComponentType::Primary,
|
||||
p.gray_2.clone(),
|
||||
p.neutral_8.clone(),
|
||||
),
|
||||
(CosmicPalette::Light(p), ContainerType::Secondary) => Self::new(
|
||||
CosmicPalette::Light(p.clone()),
|
||||
ComponentType::Secondary,
|
||||
p.gray_3.clone(),
|
||||
p.neutral_8.clone(),
|
||||
),
|
||||
(CosmicPalette::HighContrastLight(p), ContainerType::Background) => Self::new(
|
||||
CosmicPalette::HighContrastLight(p.clone()),
|
||||
ComponentType::Background,
|
||||
p.gray_1.clone(),
|
||||
p.neutral_10.clone(),
|
||||
),
|
||||
(CosmicPalette::HighContrastLight(p), ContainerType::Primary) => Self::new(
|
||||
CosmicPalette::HighContrastLight(p.clone()),
|
||||
ComponentType::Primary,
|
||||
p.gray_2.clone(),
|
||||
p.neutral_9.clone(),
|
||||
),
|
||||
(CosmicPalette::HighContrastLight(p), ContainerType::Secondary) => Self::new(
|
||||
CosmicPalette::HighContrastLight(p.clone()),
|
||||
ComponentType::Secondary,
|
||||
p.gray_3.clone(),
|
||||
p.neutral_9.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of the container
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Deserialize, Serialize)]
|
||||
pub enum ContainerType {
|
||||
/// Background type
|
||||
Background,
|
||||
/// Primary type
|
||||
Primary,
|
||||
/// Secondary type
|
||||
Secondary,
|
||||
}
|
||||
|
||||
impl Default for ContainerType {
|
||||
fn default() -> Self {
|
||||
Self::Background
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ContainerType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match *self {
|
||||
ContainerType::Background => write!(f, "Background"),
|
||||
ContainerType::Primary => write!(f, "Primary Container"),
|
||||
ContainerType::Secondary => write!(f, "Secondary Container"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The colors for a widget of the Cosmic theme
|
||||
#[derive(Clone, PartialEq, Debug, Default, Deserialize, Serialize)]
|
||||
pub struct Component<C> {
|
||||
/// The base color of the widget
|
||||
pub base: C,
|
||||
/// The color of the widget when it is hovered
|
||||
pub hover: C,
|
||||
/// the color of the widget when it is pressed
|
||||
pub pressed: C,
|
||||
/// the color of the widget when it is selected
|
||||
pub selected: C,
|
||||
/// the color of the widget when it is selected
|
||||
pub selected_text: C,
|
||||
/// the color of the widget when it is focused
|
||||
pub focus: C,
|
||||
/// the color of dividers for this widget
|
||||
pub divider: C,
|
||||
/// the color of text for this widget
|
||||
pub on: C,
|
||||
// the color of text with opacity 80 for this widget
|
||||
// pub text_opacity_80: C,
|
||||
/// the color of the widget when it is disabled
|
||||
pub disabled: C,
|
||||
/// the color of text in the widget when it is disabled
|
||||
pub on_disabled: C,
|
||||
}
|
||||
|
||||
impl<C> Component<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
/// get @hover_state_color
|
||||
pub fn hover_state_color(&self) -> Srgba {
|
||||
self.hover.clone().into()
|
||||
}
|
||||
/// get @pressed_state_color
|
||||
pub fn pressed_state_color(&self) -> Srgba {
|
||||
self.pressed.clone().into()
|
||||
}
|
||||
/// get @selected_state_color
|
||||
pub fn selected_state_color(&self) -> Srgba {
|
||||
self.selected.clone().into()
|
||||
}
|
||||
/// get @selected_state_text_color
|
||||
pub fn selected_state_text_color(&self) -> Srgba {
|
||||
self.selected_text.clone().into()
|
||||
}
|
||||
/// get @focus_color
|
||||
pub fn focus_color(&self) -> Srgba {
|
||||
self.focus.clone().into()
|
||||
}
|
||||
/// convert to srgba
|
||||
pub fn into_srgba(self) -> Component<Srgba> {
|
||||
Component {
|
||||
base: self.base.into(),
|
||||
hover: self.hover.into(),
|
||||
pressed: self.pressed.into(),
|
||||
selected: self.selected.into(),
|
||||
selected_text: self.selected_text.into(),
|
||||
focus: self.focus.into(),
|
||||
divider: self.divider.into(),
|
||||
on: self.on.into(),
|
||||
disabled: self.disabled.into(),
|
||||
on_disabled: self.on_disabled.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// helper for producing a component from a base color a neutral and an accent
|
||||
pub fn colored_component(base: C, neutral: C, accent: C) -> Self {
|
||||
let neutral = neutral.clone().into();
|
||||
let mut neutral_05 = neutral.clone();
|
||||
let mut neutral_10 = neutral.clone();
|
||||
let mut neutral_20 = neutral.clone();
|
||||
neutral_05.alpha = 0.05;
|
||||
neutral_10.alpha = 0.1;
|
||||
neutral_20.alpha = 0.2;
|
||||
|
||||
let base: Srgba = base.into();
|
||||
let mut base_50 = base.clone();
|
||||
base_50.alpha = 0.5;
|
||||
|
||||
let on_20 = neutral.clone();
|
||||
let mut on_50 = on_20.clone();
|
||||
|
||||
on_50.alpha = 0.5;
|
||||
|
||||
Component {
|
||||
base: base.clone().into(),
|
||||
hover: over(neutral_10, base).into(),
|
||||
pressed: over(neutral_20, base).into(),
|
||||
selected: over(neutral_10, base).into(),
|
||||
selected_text: accent.clone(),
|
||||
divider: on_20.into(),
|
||||
on: neutral.into(),
|
||||
disabled: base_50.into(),
|
||||
on_disabled: on_50.into(),
|
||||
focus: accent,
|
||||
}
|
||||
}
|
||||
|
||||
/// helper for producing a component color theme
|
||||
pub fn component(
|
||||
base: C,
|
||||
component_state_overlay: C,
|
||||
base_overlay: C,
|
||||
base_overlay_alpha: f32,
|
||||
accent: C,
|
||||
on_component: C,
|
||||
is_high_contrast: bool,
|
||||
) -> Self {
|
||||
let component_state_overlay = component_state_overlay.clone().into();
|
||||
let mut component_state_overlay_10 = component_state_overlay.clone();
|
||||
let mut component_state_overlay_20 = component_state_overlay.clone();
|
||||
component_state_overlay_10.alpha = 0.1;
|
||||
component_state_overlay_20.alpha = 0.2;
|
||||
|
||||
let base = base.into();
|
||||
let mut base_overlay = base_overlay.into();
|
||||
base_overlay.alpha = base_overlay_alpha;
|
||||
let base = over(base_overlay, base);
|
||||
let mut base_50 = base.clone();
|
||||
base_50.alpha = 0.5;
|
||||
|
||||
let mut on_20 = on_component.clone().into();
|
||||
let mut on_50 = on_20.clone();
|
||||
|
||||
on_20.alpha = 0.2;
|
||||
on_50.alpha = 0.5;
|
||||
|
||||
Component {
|
||||
base: base.clone().into(),
|
||||
hover: over(component_state_overlay_10, base).into(),
|
||||
pressed: over(component_state_overlay_20, base).into(),
|
||||
selected: over(component_state_overlay_10, base).into(),
|
||||
selected_text: accent.clone(),
|
||||
focus: accent.clone(),
|
||||
divider: if is_high_contrast {
|
||||
on_50.clone().into()
|
||||
} else {
|
||||
on_20.into()
|
||||
},
|
||||
on: on_component.clone(),
|
||||
disabled: base_50.into(),
|
||||
on_disabled: on_50.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Derived theme element from a palette and constraints
|
||||
#[derive(Debug)]
|
||||
pub struct Derivation<E> {
|
||||
/// Derived theme element
|
||||
pub derived: E,
|
||||
/// Derivation errors (Failed constraints)
|
||||
pub errors: Vec<anyhow::Error>,
|
||||
}
|
||||
|
||||
pub(crate) enum ComponentType {
|
||||
Background,
|
||||
Primary,
|
||||
Secondary,
|
||||
Destructive,
|
||||
Warning,
|
||||
Success,
|
||||
Accent,
|
||||
}
|
||||
|
||||
impl<C> From<(CosmicPalette<C>, ComponentType)> for Component<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
fn from((p, t): (CosmicPalette<C>, ComponentType)) -> Self {
|
||||
match (p, t) {
|
||||
(CosmicPalette::Dark(p), ComponentType::Background) => Self::component(
|
||||
p.gray_1,
|
||||
p.neutral_1,
|
||||
p.neutral_10,
|
||||
0.08,
|
||||
p.blue,
|
||||
p.neutral_8,
|
||||
false,
|
||||
),
|
||||
|
||||
(CosmicPalette::Dark(p), ComponentType::Primary) => Self::component(
|
||||
p.gray_2,
|
||||
p.neutral_1,
|
||||
p.neutral_10,
|
||||
0.08,
|
||||
p.blue,
|
||||
p.neutral_8,
|
||||
false,
|
||||
),
|
||||
|
||||
(CosmicPalette::Dark(p), ComponentType::Secondary) => Self::component(
|
||||
p.gray_3,
|
||||
p.neutral_1,
|
||||
p.neutral_10,
|
||||
0.08,
|
||||
p.blue,
|
||||
p.neutral_9,
|
||||
false,
|
||||
),
|
||||
(CosmicPalette::HighContrastDark(p), ComponentType::Background) => Self::component(
|
||||
p.gray_1,
|
||||
p.neutral_1,
|
||||
p.neutral_10,
|
||||
0.08,
|
||||
p.blue,
|
||||
p.neutral_9,
|
||||
true,
|
||||
),
|
||||
(CosmicPalette::HighContrastDark(p), ComponentType::Primary) => Self::component(
|
||||
p.gray_2,
|
||||
p.neutral_1,
|
||||
p.neutral_10,
|
||||
0.08,
|
||||
p.blue,
|
||||
p.neutral_9,
|
||||
true,
|
||||
),
|
||||
(CosmicPalette::HighContrastDark(p), ComponentType::Secondary) => Self::component(
|
||||
p.gray_3,
|
||||
p.neutral_1,
|
||||
p.neutral_10.clone(),
|
||||
0.08,
|
||||
p.blue,
|
||||
p.neutral_10,
|
||||
true,
|
||||
),
|
||||
|
||||
(CosmicPalette::Light(p), ComponentType::Background) => Component::component(
|
||||
p.gray_1.clone(),
|
||||
p.neutral_1.clone(),
|
||||
p.neutral_1,
|
||||
0.75,
|
||||
p.blue.clone(),
|
||||
p.neutral_8,
|
||||
false,
|
||||
),
|
||||
(CosmicPalette::Light(p), ComponentType::Primary) => Component::component(
|
||||
p.gray_2.clone(),
|
||||
p.neutral_1.clone(),
|
||||
p.neutral_1,
|
||||
0.9,
|
||||
p.blue.clone(),
|
||||
p.neutral_8,
|
||||
false,
|
||||
),
|
||||
(CosmicPalette::Light(p), ComponentType::Secondary) => Component::component(
|
||||
p.gray_3.clone(),
|
||||
p.neutral_1.clone(),
|
||||
p.neutral_1,
|
||||
1.0,
|
||||
p.blue.clone(),
|
||||
p.neutral_8,
|
||||
false,
|
||||
),
|
||||
(CosmicPalette::HighContrastLight(p), ComponentType::Background) => {
|
||||
Component::component(
|
||||
p.gray_1.clone(),
|
||||
p.neutral_1.clone(),
|
||||
p.neutral_1,
|
||||
0.75,
|
||||
p.blue.clone(),
|
||||
p.neutral_9,
|
||||
true,
|
||||
)
|
||||
}
|
||||
(CosmicPalette::HighContrastLight(p), ComponentType::Primary) => Component::component(
|
||||
p.gray_2.clone(),
|
||||
p.neutral_1.clone(),
|
||||
p.neutral_1,
|
||||
0.9,
|
||||
p.blue.clone(),
|
||||
p.neutral_9,
|
||||
true,
|
||||
),
|
||||
(CosmicPalette::HighContrastLight(p), ComponentType::Secondary) => {
|
||||
Component::component(
|
||||
p.gray_3.clone(),
|
||||
p.neutral_1.clone(),
|
||||
p.neutral_1,
|
||||
1.0,
|
||||
p.blue.clone(),
|
||||
p.neutral_9,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
(CosmicPalette::Dark(p), ComponentType::Destructive)
|
||||
| (CosmicPalette::Light(p), ComponentType::Destructive)
|
||||
| (CosmicPalette::HighContrastLight(p), ComponentType::Destructive)
|
||||
| (CosmicPalette::HighContrastDark(p), ComponentType::Destructive) => {
|
||||
Component::colored_component(p.red.clone(), p.neutral_1.clone(), p.blue.clone())
|
||||
}
|
||||
|
||||
(CosmicPalette::Dark(p), ComponentType::Warning)
|
||||
| (CosmicPalette::Light(p), ComponentType::Warning)
|
||||
| (CosmicPalette::HighContrastLight(p), ComponentType::Warning)
|
||||
| (CosmicPalette::HighContrastDark(p), ComponentType::Warning) => {
|
||||
Component::colored_component(p.yellow.clone(), p.neutral_1, p.blue.clone())
|
||||
}
|
||||
|
||||
(CosmicPalette::Dark(p), ComponentType::Success)
|
||||
| (CosmicPalette::Light(p), ComponentType::Success)
|
||||
| (CosmicPalette::HighContrastLight(p), ComponentType::Success)
|
||||
| (CosmicPalette::HighContrastDark(p), ComponentType::Success) => {
|
||||
Component::colored_component(p.green.clone(), p.neutral_1, p.blue.clone())
|
||||
}
|
||||
|
||||
(CosmicPalette::Dark(p), ComponentType::Accent)
|
||||
| (CosmicPalette::Light(p), ComponentType::Accent)
|
||||
| (CosmicPalette::HighContrastDark(p), ComponentType::Accent)
|
||||
| (CosmicPalette::HighContrastLight(p), ComponentType::Accent) => {
|
||||
Component::colored_component(p.blue.clone(), p.neutral_1, p.blue.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
cosmic-theme/src/model/light.ron
Normal file
95
cosmic-theme/src/model/light.ron
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
Light (
|
||||
(
|
||||
name: "cosmic-light",
|
||||
blue: (
|
||||
c: "#00496D",
|
||||
),
|
||||
red: (
|
||||
c: "#A0252B",
|
||||
),
|
||||
green: (
|
||||
c: "#3B6E43",
|
||||
),
|
||||
yellow: (
|
||||
c: "#966800",
|
||||
),
|
||||
gray_1: (
|
||||
c: "#DEDEDE",
|
||||
),
|
||||
gray_2: (
|
||||
c: "#E9E9E9",
|
||||
),
|
||||
gray_3: (
|
||||
c: "#F4F4F4",
|
||||
),
|
||||
neutral_1: (
|
||||
c: "#FFFFFF",
|
||||
),
|
||||
neutral_2: (
|
||||
c: "#E4E4E4",
|
||||
),
|
||||
neutral_3: (
|
||||
c: "#C9C9C9",
|
||||
),
|
||||
neutral_4: (
|
||||
c: "#AEAEAE",
|
||||
),
|
||||
neutral_5: (
|
||||
c: "#939393",
|
||||
),
|
||||
neutral_6: (
|
||||
c: "#787878",
|
||||
),
|
||||
neutral_7: (
|
||||
c: "#5D5D5D",
|
||||
),
|
||||
neutral_8: (
|
||||
c: "#424242",
|
||||
),
|
||||
neutral_9: (
|
||||
c: "#272727",
|
||||
),
|
||||
neutral_10: (
|
||||
c: "#000000",
|
||||
),
|
||||
ext_warm_grey: (
|
||||
c: "#9B8E8A",
|
||||
),
|
||||
ext_orange: (
|
||||
c: "#FBB86C",
|
||||
),
|
||||
ext_yellow: (
|
||||
c: "#F7E062",
|
||||
),
|
||||
ext_blue: (
|
||||
c: "#6ACAD8",
|
||||
),
|
||||
ext_purple: (
|
||||
c: "#D58CFF",
|
||||
),
|
||||
ext_pink: (
|
||||
c: "#FF9CDD",
|
||||
),
|
||||
ext_indigo: (
|
||||
c: "#95C4FC",
|
||||
),
|
||||
accent_warm_grey: (
|
||||
c: "#ADA29E",
|
||||
),
|
||||
accent_orange: (
|
||||
c: "#FFD7A1",
|
||||
),
|
||||
accent_yellow: (
|
||||
c: "#FFF19E",
|
||||
),
|
||||
accent_purple: (
|
||||
c: "#D58CFF",
|
||||
),
|
||||
accent_pink: (
|
||||
c: "#FF9CDD",
|
||||
),
|
||||
accent_indigo: (
|
||||
c: "#95C4FC",
|
||||
),
|
||||
)
|
||||
)
|
||||
14
cosmic-theme/src/model/mod.rs
Normal file
14
cosmic-theme/src/model/mod.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#[cfg(feature = "contrast-derivation")]
|
||||
pub use constraint::*;
|
||||
pub use cosmic_palette::*;
|
||||
pub use derivation::*;
|
||||
#[cfg(feature = "contrast-derivation")]
|
||||
pub use selection::*;
|
||||
pub use theme::*;
|
||||
#[cfg(feature = "contrast-derivation")]
|
||||
mod constraint;
|
||||
mod cosmic_palette;
|
||||
mod derivation;
|
||||
#[cfg(feature = "contrast-derivation")]
|
||||
mod selection;
|
||||
mod theme;
|
||||
99
cosmic-theme/src/model/selection.rs
Normal file
99
cosmic-theme/src/model/selection.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use palette::{named, IntoColor, Lch, Srgba};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
/// A Selection is a group of colors from which a cosmic palette can be derived
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct Selection<C> {
|
||||
/// base background container color
|
||||
pub background: C,
|
||||
/// base primary container color
|
||||
pub primary_container: C,
|
||||
/// base secondary container color
|
||||
pub secondary_container: C,
|
||||
/// base accent color
|
||||
pub accent: C,
|
||||
/// custom accent color (overrides base)
|
||||
pub accent_fg: Option<C>,
|
||||
/// custom accent nav handle text color (overrides base)
|
||||
pub accent_nav_handle_fg: Option<C>,
|
||||
/// base destructive element color
|
||||
pub destructive: C,
|
||||
/// base destructive element color
|
||||
pub warning: C,
|
||||
/// base destructive element color
|
||||
pub success: C,
|
||||
}
|
||||
|
||||
// vector should be in order of most common
|
||||
impl<C> TryFrom<Vec<Srgba>> for Selection<C>
|
||||
where
|
||||
C: Clone + From<Srgba>,
|
||||
{
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(mut colors: Vec<Srgba>) -> Result<Self, Self::Error> {
|
||||
if colors.len() < 8 {
|
||||
anyhow::bail!("length of inputted vector must be at least 8.")
|
||||
} else {
|
||||
let lch_colors: Vec<Lch> = colors
|
||||
.iter()
|
||||
.map(|x| {
|
||||
let srgba: Srgba = x.clone().into();
|
||||
srgba.color.into_format().into_color()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let red_lch: Lch = named::CRIMSON.into_format().into_color();
|
||||
let mut reddest_i = 1;
|
||||
for (i, c) in lch_colors[1..].iter().enumerate() {
|
||||
let d_cur = (c.hue.to_degrees() - red_lch.hue.to_degrees()).abs();
|
||||
let reddest_d = (lch_colors[reddest_i].hue.to_degrees().abs()
|
||||
- red_lch.hue.to_degrees().abs())
|
||||
.abs();
|
||||
if d_cur < reddest_d {
|
||||
reddest_i = i;
|
||||
}
|
||||
}
|
||||
|
||||
let yellow_lch: Lch = named::YELLOW.into_format().into_color();
|
||||
let mut yellow_i = 1;
|
||||
for (i, c) in lch_colors[1..].iter().enumerate() {
|
||||
let d_cur = (c.hue.to_degrees() - yellow_lch.hue.to_degrees()).abs();
|
||||
let reddest_d = (lch_colors[yellow_i].hue.to_degrees().abs()
|
||||
- yellow_lch.hue.to_degrees().abs())
|
||||
.abs();
|
||||
if d_cur < reddest_d {
|
||||
yellow_i = i;
|
||||
}
|
||||
}
|
||||
|
||||
let green_lch: Lch = named::GREEN.into_format().into_color();
|
||||
let mut green_i = 1;
|
||||
for (i, c) in lch_colors[1..].iter().enumerate() {
|
||||
let d_cur = (c.hue.to_degrees() - green_lch.hue.to_degrees()).abs();
|
||||
let reddest_d = (lch_colors[green_i].hue.to_degrees().abs()
|
||||
- green_lch.hue.to_degrees().abs())
|
||||
.abs();
|
||||
if d_cur < reddest_d {
|
||||
green_i = i;
|
||||
}
|
||||
}
|
||||
|
||||
let red = colors.remove(reddest_i);
|
||||
let green = colors.remove(green_i);
|
||||
let yellow = colors.remove(yellow_i);
|
||||
|
||||
Ok(Self {
|
||||
background: colors[0].into(),
|
||||
primary_container: colors[1].into(),
|
||||
secondary_container: colors[3].into(),
|
||||
accent: colors[2].into(),
|
||||
accent_fg: Some(colors[2].into()),
|
||||
accent_nav_handle_fg: Some(colors[2].into()),
|
||||
destructive: red.into(),
|
||||
warning: yellow.into(),
|
||||
success: green.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
431
cosmic-theme/src/model/theme.rs
Normal file
431
cosmic-theme/src/model/theme.rs
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
use crate::{
|
||||
util::CssColor, Component, ComponentType, Container, ContainerType, CosmicPalette,
|
||||
CosmicPaletteInner, DARK_PALETTE, LIGHT_PALETTE, NAME, THEME_DIR,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||
use directories::{BaseDirsExt, ProjectDirsExt};
|
||||
use palette::Srgba;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt,
|
||||
fs::File,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
/// Theme layer type
|
||||
pub enum Layer {
|
||||
/// Background layer
|
||||
#[default]
|
||||
Background,
|
||||
/// Primary Layer
|
||||
Primary,
|
||||
/// Secondary Layer
|
||||
Secondary,
|
||||
}
|
||||
|
||||
/// Cosmic Theme data structure with all colors and its name
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Theme<C> {
|
||||
/// name of the theme
|
||||
pub name: String,
|
||||
/// background element colors
|
||||
pub background: Container<C>,
|
||||
/// primary element colors
|
||||
pub primary: Container<C>,
|
||||
/// secondary element colors
|
||||
pub secondary: Container<C>,
|
||||
/// accent element colors
|
||||
pub accent: Component<C>,
|
||||
/// suggested element colors
|
||||
pub success: Component<C>,
|
||||
/// destructive element colors
|
||||
pub destructive: Component<C>,
|
||||
/// warning element colors
|
||||
pub warning: Component<C>,
|
||||
/// palette
|
||||
pub palette: CosmicPaletteInner<C>,
|
||||
/// is dark
|
||||
pub is_dark: bool,
|
||||
/// is high contrast
|
||||
pub is_high_contrast: bool,
|
||||
}
|
||||
|
||||
impl CosmicConfigEntry for Theme<CssColor> {
|
||||
fn write_entry(&self, config: &Config) -> Result<(), cosmic_config::Error> {
|
||||
let self_ = self.clone();
|
||||
// TODO do as transaction
|
||||
let tx = config.transaction();
|
||||
|
||||
tx.set("name", self_.name)?;
|
||||
tx.set("background", self_.background)?;
|
||||
tx.set("primary", self_.primary)?;
|
||||
tx.set("secondary", self_.secondary)?;
|
||||
tx.set("accent", self_.accent)?;
|
||||
tx.set("success", self_.success)?;
|
||||
tx.set("destructive", self_.destructive)?;
|
||||
tx.set("warning", self_.warning)?;
|
||||
tx.set("palette", self_.palette)?;
|
||||
tx.set("is_dark", self_.is_dark)?;
|
||||
tx.set("is_high_contrast", self_.is_high_contrast)?;
|
||||
|
||||
tx.commit()
|
||||
}
|
||||
|
||||
fn get_entry(config: &Config) -> Result<Self, (Vec<cosmic_config::Error>, Self)> {
|
||||
let mut default = Self::default();
|
||||
let mut errors = Vec::new();
|
||||
|
||||
match config.get::<String>("name") {
|
||||
Ok(name) => default.name = name,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Container<CssColor>>("background") {
|
||||
Ok(background) => default.background = background,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Container<CssColor>>("primary") {
|
||||
Ok(primary) => default.primary = primary,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Container<CssColor>>("secondary") {
|
||||
Ok(secondary) => default.secondary = secondary,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<CssColor>>("accent") {
|
||||
Ok(accent) => default.accent = accent,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<CssColor>>("success") {
|
||||
Ok(success) => default.success = success,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<CssColor>>("destructive") {
|
||||
Ok(destructive) => default.destructive = destructive,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<Component<CssColor>>("warning") {
|
||||
Ok(warning) => default.warning = warning,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<CosmicPaletteInner<CssColor>>("palette") {
|
||||
Ok(palette) => default.palette = palette,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<bool>("is_dark") {
|
||||
Ok(is_dark) => default.is_dark = is_dark,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
match config.get::<bool>("is_high_contrast") {
|
||||
Ok(is_high_contrast) => default.is_high_contrast = is_high_contrast,
|
||||
Err(e) => errors.push(e),
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
Ok(default)
|
||||
} else {
|
||||
Err((errors, default))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Theme<Srgba> {
|
||||
fn default() -> Self {
|
||||
Theme::<CssColor>::dark_default().into_srgba()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Theme<CssColor> {
|
||||
fn default() -> Self {
|
||||
Self::dark_default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for layered themes
|
||||
pub trait LayeredTheme {
|
||||
/// Set the layer of the theme
|
||||
fn set_layer(&mut self, layer: Layer);
|
||||
}
|
||||
|
||||
// TODO better eq check
|
||||
impl<C> PartialEq for Theme<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Eq for Theme<C> where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned
|
||||
{
|
||||
}
|
||||
|
||||
impl<C> Theme<C> {
|
||||
/// version of the theme
|
||||
pub fn version() -> u32 {
|
||||
1
|
||||
}
|
||||
|
||||
/// id of the theme
|
||||
pub fn id() -> &'static str {
|
||||
NAME
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Theme<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
/// Convert the theme to a high-contrast variant
|
||||
pub fn to_high_contrast(&self) -> Self {
|
||||
todo!();
|
||||
}
|
||||
|
||||
/// save the theme to the theme directory
|
||||
pub fn save(&self) -> anyhow::Result<()> {
|
||||
let ron_path: PathBuf = [NAME, THEME_DIR].iter().collect();
|
||||
let ron_dirs = directories::ProjectDirs::from_path(ron_path)
|
||||
.context("Failed to get project directories.")?;
|
||||
let ron_name = format!("{}.ron", &self.name);
|
||||
|
||||
if let Ok(p) = ron_dirs.place_config_file(ron_name) {
|
||||
let mut f = File::create(p)?;
|
||||
f.write_all(ron::ser::to_string_pretty(self, Default::default())?.as_bytes())?;
|
||||
} else {
|
||||
anyhow::bail!("Failed to write RON theme.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// init the theme directory
|
||||
pub fn init() -> anyhow::Result<PathBuf> {
|
||||
let ron_path: PathBuf = [NAME, THEME_DIR].iter().collect();
|
||||
let base_dirs = directories::BaseDirs::new().context("Failed to get base directories.")?;
|
||||
Ok(base_dirs.create_config_directory(ron_path)?)
|
||||
}
|
||||
|
||||
/// load a theme by name
|
||||
pub fn load_from_name(name: &str) -> anyhow::Result<Self> {
|
||||
let ron_path: PathBuf = [NAME, THEME_DIR].iter().collect();
|
||||
let ron_dirs = directories::ProjectDirs::from_path(ron_path)
|
||||
.context("Failed to get project directories.")?;
|
||||
|
||||
let ron_name = format!("{}.ron", name);
|
||||
if let Some(p) = ron_dirs.find_config_file(ron_name) {
|
||||
let f = File::open(p)?;
|
||||
Ok(ron::de::from_reader(f)?)
|
||||
} else {
|
||||
anyhow::bail!("Failed to write RON theme.");
|
||||
}
|
||||
}
|
||||
|
||||
/// load a theme by path
|
||||
pub fn load(p: &dyn AsRef<Path>) -> anyhow::Result<Self> {
|
||||
let f = File::open(p)?;
|
||||
Ok(ron::de::from_reader(f)?)
|
||||
}
|
||||
|
||||
// TODO convenient getter functions for each named color variable
|
||||
/// get @accent_color
|
||||
pub fn accent_color(&self) -> Srgba {
|
||||
self.accent.base.clone().into()
|
||||
}
|
||||
/// get @success_color
|
||||
pub fn success_color(&self) -> Srgba {
|
||||
self.success.base.clone().into()
|
||||
}
|
||||
/// get @destructive_color
|
||||
pub fn destructive_color(&self) -> Srgba {
|
||||
self.destructive.base.clone().into()
|
||||
}
|
||||
/// get @warning_color
|
||||
pub fn warning_color(&self) -> Srgba {
|
||||
self.warning.base.clone().into()
|
||||
}
|
||||
|
||||
// Containers
|
||||
/// get @bg_color
|
||||
pub fn bg_color(&self) -> Srgba {
|
||||
self.background.base.clone().into()
|
||||
}
|
||||
/// get @bg_component_color
|
||||
pub fn bg_component_color(&self) -> Srgba {
|
||||
self.background.component.base.clone().into()
|
||||
}
|
||||
/// get @primary_container_color
|
||||
pub fn primary_container_color(&self) -> Srgba {
|
||||
self.primary.base.clone().into()
|
||||
}
|
||||
/// get @primary_component_color
|
||||
pub fn primary_component_color(&self) -> Srgba {
|
||||
self.primary.component.base.clone().into()
|
||||
}
|
||||
/// get @secondary_container_color
|
||||
pub fn secondary_container_color(&self) -> Srgba {
|
||||
self.secondary.base.clone().into()
|
||||
}
|
||||
/// get @secondary_component_color
|
||||
pub fn secondary_component_color(&self) -> Srgba {
|
||||
self.secondary.component.base.clone().into()
|
||||
}
|
||||
|
||||
// Text
|
||||
/// get @on_bg_color
|
||||
pub fn on_bg_color(&self) -> Srgba {
|
||||
self.background.on.clone().into()
|
||||
}
|
||||
/// get @on_bg_component_color
|
||||
pub fn on_bg_component_color(&self) -> Srgba {
|
||||
self.background.component.on.clone().into()
|
||||
}
|
||||
/// get @on_primary_color
|
||||
pub fn on_primary_container_color(&self) -> Srgba {
|
||||
self.primary.on.clone().into()
|
||||
}
|
||||
/// get @on_primary_component_color
|
||||
pub fn on_primary_component_color(&self) -> Srgba {
|
||||
self.primary.component.on.clone().into()
|
||||
}
|
||||
/// get @on_secondary_color
|
||||
pub fn on_secondary_container_color(&self) -> Srgba {
|
||||
self.secondary.on.clone().into()
|
||||
}
|
||||
/// get @on_secondary_component_color
|
||||
pub fn on_secondary_component_color(&self) -> Srgba {
|
||||
self.secondary.component.on.clone().into()
|
||||
}
|
||||
/// get @accent_text_color
|
||||
pub fn accent_text_color(&self) -> Srgba {
|
||||
self.accent.base.clone().into()
|
||||
}
|
||||
/// get @success_text_color
|
||||
pub fn success_text_color(&self) -> Srgba {
|
||||
self.success.base.clone().into()
|
||||
}
|
||||
/// get @warning_text_color
|
||||
pub fn warning_text_color(&self) -> Srgba {
|
||||
self.warning.base.clone().into()
|
||||
}
|
||||
/// get @destructive_text_color
|
||||
pub fn destructive_text_color(&self) -> Srgba {
|
||||
self.destructive.base.clone().into()
|
||||
}
|
||||
/// get @on_accent_color
|
||||
pub fn on_accent_color(&self) -> Srgba {
|
||||
self.accent.on.clone().into()
|
||||
}
|
||||
/// get @on_success_color
|
||||
pub fn on_success_color(&self) -> Srgba {
|
||||
self.success.on.clone().into()
|
||||
}
|
||||
/// get @oon_warning_color
|
||||
pub fn on_warning_color(&self) -> Srgba {
|
||||
self.warning.on.clone().into()
|
||||
}
|
||||
/// get @on_destructive_color
|
||||
pub fn on_destructive_color(&self) -> Srgba {
|
||||
self.destructive.on.clone().into()
|
||||
}
|
||||
|
||||
// Borders and Dividers
|
||||
/// get @bg_divider
|
||||
pub fn bg_divider(&self) -> Srgba {
|
||||
self.background.divider.clone().into()
|
||||
}
|
||||
/// get @bg_component_divider
|
||||
pub fn bg_component_divider(&self) -> Srgba {
|
||||
self.background.component.divider.clone().into()
|
||||
}
|
||||
/// get @primary_container_divider
|
||||
pub fn primary_container_divider(&self) -> Srgba {
|
||||
self.primary.divider.clone().into()
|
||||
}
|
||||
/// get @primary_component_divider
|
||||
pub fn primary_component_divider(&self) -> Srgba {
|
||||
self.primary.component.divider.clone().into()
|
||||
}
|
||||
/// get @secondary_container_divider
|
||||
pub fn secondary_container_divider(&self) -> Srgba {
|
||||
self.secondary.divider.clone().into()
|
||||
}
|
||||
/// get @secondary_component_divider
|
||||
pub fn secondary_component_divider(&self) -> Srgba {
|
||||
self.secondary.component.divider.clone().into()
|
||||
}
|
||||
|
||||
/// get @window_header_bg
|
||||
pub fn window_header_bg(&self) -> Srgba {
|
||||
self.background.base.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme<CssColor> {
|
||||
/// get the built in light theme
|
||||
pub fn light_default() -> Self {
|
||||
LIGHT_PALETTE.clone().into()
|
||||
}
|
||||
|
||||
/// get the built in dark theme
|
||||
pub fn dark_default() -> Self {
|
||||
DARK_PALETTE.clone().into()
|
||||
}
|
||||
|
||||
/// get the built in high contrast dark theme
|
||||
pub fn high_contrast_dark_default() -> Self {
|
||||
CosmicPalette::HighContrastDark(DARK_PALETTE.as_ref().clone()).into()
|
||||
}
|
||||
|
||||
/// get the built in high contrast light theme
|
||||
pub fn high_contrast_light_default() -> Self {
|
||||
CosmicPalette::HighContrastLight(LIGHT_PALETTE.as_ref().clone()).into()
|
||||
}
|
||||
|
||||
/// convert to srgba
|
||||
pub fn into_srgba(self) -> Theme<Srgba> {
|
||||
Theme {
|
||||
name: self.name,
|
||||
background: self.background.into_srgba(),
|
||||
primary: self.primary.into_srgba(),
|
||||
secondary: self.secondary.into_srgba(),
|
||||
accent: self.accent.into_srgba(),
|
||||
success: self.success.into_srgba(),
|
||||
destructive: self.destructive.into_srgba(),
|
||||
warning: self.warning.into_srgba(),
|
||||
palette: self.palette.into(),
|
||||
is_dark: self.is_dark,
|
||||
is_high_contrast: self.is_high_contrast,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> From<CosmicPalette<C>> for Theme<C>
|
||||
where
|
||||
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
|
||||
{
|
||||
fn from(p: CosmicPalette<C>) -> Self {
|
||||
let is_dark = p.is_dark();
|
||||
let is_high_contrast = p.is_high_contrast();
|
||||
Self {
|
||||
name: p.name().to_string(),
|
||||
background: (p.clone(), ContainerType::Background).into(),
|
||||
primary: (p.clone(), ContainerType::Primary).into(),
|
||||
secondary: (p.clone(), ContainerType::Secondary).into(),
|
||||
accent: (p.clone(), ComponentType::Accent).into(),
|
||||
success: (p.clone(), ComponentType::Success).into(),
|
||||
destructive: (p.clone(), ComponentType::Destructive).into(),
|
||||
warning: (p.clone(), ComponentType::Warning).into(),
|
||||
palette: match p {
|
||||
CosmicPalette::Dark(p) => p.into(),
|
||||
CosmicPalette::Light(p) => p.into(),
|
||||
CosmicPalette::HighContrastLight(p) => p.into(),
|
||||
CosmicPalette::HighContrastDark(p) => p.into(),
|
||||
},
|
||||
is_dark,
|
||||
is_high_contrast,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue