diff --git a/cosmic-theme/src/model/corner.rs b/cosmic-theme/src/model/corner.rs new file mode 100644 index 00000000..e466959d --- /dev/null +++ b/cosmic-theme/src/model/corner.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; + +/// Corner radii variables for the Cosmic theme +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct CornerRadii { + /// corner radii of 0 + pub radius_0: [u16; 4], + /// smallest size of corner radii that can be non-zero + pub radius_xs: [u16; 4], + /// small corner radii + pub radius_s: [u16; 4], + /// medium corner radii + pub radius_m: [u16; 4], + /// large corner radii + pub radius_l: [u16; 4], + /// extra large corner radii + pub radius_xl: [u16; 4], +} + +impl Default for CornerRadii { + fn default() -> Self { + Self { + radius_0: [0; 4], + radius_xs: [4; 4], + radius_s: [8; 4], + radius_m: [16; 4], + radius_l: [32; 4], + radius_xl: [160; 4], + } + } +} diff --git a/cosmic-theme/src/model/cosmic_palette.rs b/cosmic-theme/src/model/cosmic_palette.rs index 6ad225eb..7419e102 100644 --- a/cosmic-theme/src/model/cosmic_palette.rs +++ b/cosmic-theme/src/model/cosmic_palette.rs @@ -35,6 +35,29 @@ pub enum CosmicPalette { HighContrastDark(CosmicPaletteInner), } +impl CosmicPalette { + /// extract the inner palette + pub fn inner(self) -> CosmicPaletteInner { + match self { + CosmicPalette::Dark(p) => p, + CosmicPalette::Light(p) => p, + CosmicPalette::HighContrastLight(p) => p, + CosmicPalette::HighContrastDark(p) => p, + } + } +} + +impl AsMut> for CosmicPalette { + fn as_mut(&mut self) -> &mut CosmicPaletteInner { + match self { + CosmicPalette::Dark(p) => p, + CosmicPalette::Light(p) => p, + CosmicPalette::HighContrastLight(p) => p, + CosmicPalette::HighContrastDark(p) => p, + } + } +} + impl AsRef> for CosmicPalette where C: Clone + fmt::Debug + Default + Into + From + Serialize + DeserializeOwned, @@ -85,6 +108,9 @@ pub struct CosmicPaletteInner { /// name of the palette pub name: String, + /// the selected accent color + pub accent: C, + /// basic palette /// blue: colors used for various points of emphasis in the UI pub blue: C, @@ -161,6 +187,7 @@ impl From> for CosmicPaletteInner { fn from(p: CosmicPaletteInner) -> Self { CosmicPaletteInner { name: p.name, + accent: p.accent.into(), blue: p.blue.into(), red: p.red.into(), green: p.green.into(), @@ -253,3 +280,14 @@ where Ok(ron::de::from_reader(f)?) } } + +impl Into> for CosmicPalette { + fn into(self) -> CosmicPalette { + match self { + CosmicPalette::Dark(p) => CosmicPalette::Dark(p.into()), + CosmicPalette::Light(p) => CosmicPalette::Light(p.into()), + CosmicPalette::HighContrastLight(p) => CosmicPalette::HighContrastLight(p.into()), + CosmicPalette::HighContrastDark(p) => CosmicPalette::HighContrastDark(p.into()), + } + } +} diff --git a/cosmic-theme/src/model/dark.ron b/cosmic-theme/src/model/dark.ron index 9f283121..a7d77282 100644 --- a/cosmic-theme/src/model/dark.ron +++ b/cosmic-theme/src/model/dark.ron @@ -1,6 +1,9 @@ Dark ( ( name: "cosmic-dark", + accent: ( + c: "#94EBEB", + ), blue: ( c: "#94EBEB", ), diff --git a/cosmic-theme/src/model/derivation.rs b/cosmic-theme/src/model/derivation.rs index e6265002..1868cdba 100644 --- a/cosmic-theme/src/model/derivation.rs +++ b/cosmic-theme/src/model/derivation.rs @@ -337,7 +337,7 @@ where p.neutral_0, p.neutral_10, 0.08, - p.blue, + p.accent, p.neutral_8, false, ), @@ -347,7 +347,7 @@ where p.neutral_0, p.neutral_10, 0.08, - p.blue, + p.accent, p.neutral_8, false, ), @@ -357,7 +357,7 @@ where p.neutral_0, p.neutral_10, 0.08, - p.blue, + p.accent, p.neutral_9, false, ), @@ -366,7 +366,7 @@ where p.neutral_0, p.neutral_10, 0.08, - p.blue, + p.accent, p.neutral_9, true, ), @@ -375,7 +375,7 @@ where p.neutral_0, p.neutral_10, 0.08, - p.blue, + p.accent, p.neutral_9, true, ), @@ -384,7 +384,7 @@ where p.neutral_0, p.neutral_10.clone(), 0.08, - p.blue, + p.accent, p.neutral_10, true, ), @@ -394,7 +394,7 @@ where p.neutral_0.clone(), p.neutral_0, 0.75, - p.blue.clone(), + p.accent.clone(), p.neutral_8, false, ), @@ -403,7 +403,7 @@ where p.neutral_0.clone(), p.neutral_0, 0.9, - p.blue.clone(), + p.accent.clone(), p.neutral_8, false, ), @@ -412,7 +412,7 @@ where p.neutral_0.clone(), p.neutral_0, 1.0, - p.blue.clone(), + p.accent.clone(), p.neutral_8, false, ), @@ -422,7 +422,7 @@ where p.neutral_0.clone(), p.neutral_0, 0.75, - p.blue.clone(), + p.accent.clone(), p.neutral_9, true, ) @@ -432,7 +432,7 @@ where p.neutral_0.clone(), p.neutral_0, 0.9, - p.blue.clone(), + p.accent.clone(), p.neutral_9, true, ), @@ -442,7 +442,7 @@ where p.neutral_0.clone(), p.neutral_0, 1.0, - p.blue.clone(), + p.accent.clone(), p.neutral_9, true, ) diff --git a/cosmic-theme/src/model/light.ron b/cosmic-theme/src/model/light.ron index cd9f0f93..8ae7847b 100644 --- a/cosmic-theme/src/model/light.ron +++ b/cosmic-theme/src/model/light.ron @@ -1,6 +1,9 @@ Light ( ( name: "cosmic-light", + accent: ( + c: "#00496D", + ), blue: ( c: "#00496D", ), diff --git a/cosmic-theme/src/model/mod.rs b/cosmic-theme/src/model/mod.rs index 5e82c762..5751e231 100644 --- a/cosmic-theme/src/model/mod.rs +++ b/cosmic-theme/src/model/mod.rs @@ -1,7 +1,11 @@ +pub use corner::*; pub use cosmic_palette::*; pub use derivation::*; +pub use spacing::*; pub use theme::*; +mod corner; mod cosmic_palette; mod derivation; +mod spacing; mod theme; diff --git a/cosmic-theme/src/model/spacing.rs b/cosmic-theme/src/model/spacing.rs new file mode 100644 index 00000000..93b1bf43 --- /dev/null +++ b/cosmic-theme/src/model/spacing.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +/// Spacing variables for the Cosmic theme +#[derive(Debug, Copy, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub struct Spacing { + /// No spacing + pub space_none: u16, + /// smallest spacing that can be non-zero + pub space_xxxs: u16, + /// extra extra small spacing + pub space_xxs: u16, + /// extra small spacing + pub space_xs: u16, + /// small spacing + pub space_s: u16, + /// medium spacing + pub space_m: u16, + /// large spacing + pub space_l: u16, + /// extra large spacing + pub space_xl: u16, + /// extra extra large spacing + pub space_xxl: u16, + /// largest possible spacing + pub space_xxxl: u16, +} + +impl Default for Spacing { + fn default() -> Self { + Self { + space_none: 0, + space_xxxs: 4, + space_xxs: 8, + space_xs: 12, + space_s: 16, + space_m: 24, + space_l: 32, + space_xl: 48, + space_xxl: 64, + space_xxxl: 128, + } + } +} diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 284dd6c6..503b12d5 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,11 +1,11 @@ use crate::{ - util::CssColor, Component, ComponentType, Container, ContainerType, CosmicPalette, - CosmicPaletteInner, DARK_PALETTE, LIGHT_PALETTE, NAME, THEME_DIR, + util::CssColor, Component, ComponentType, Container, ContainerType, CornerRadii, CosmicPalette, + CosmicPaletteInner, Spacing, 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 palette::{Srgb, Srgba}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ fmt, @@ -47,6 +47,10 @@ pub struct Theme { pub warning: Component, /// palette pub palette: CosmicPaletteInner, + /// spacing + pub spacing: Spacing, + /// corner radii + pub corner_radii: CornerRadii, /// is dark pub is_dark: bool, /// is high contrast @@ -122,6 +126,14 @@ impl CosmicConfigEntry for Theme { Ok(is_high_contrast) => default.is_high_contrast = is_high_contrast, Err(e) => errors.push(e), } + match config.get::("spacing") { + Ok(spacing) => default.spacing = spacing, + Err(e) => errors.push(e), + } + match config.get::("corner_radii") { + Ok(corner_radii) => default.corner_radii = corner_radii, + Err(e) => errors.push(e), + } if errors.is_empty() { Ok(default) @@ -346,6 +358,72 @@ where pub fn window_header_bg(&self) -> Srgba { self.background.base.clone().into() } + + /// get @space_none + pub fn space_none(&self) -> u16 { + self.spacing.space_none + } + /// get @space_xxxs + pub fn space_xxxs(&self) -> u16 { + self.spacing.space_xxxs + } + /// get @space_xxs + pub fn space_xxs(&self) -> u16 { + self.spacing.space_xxs + } + /// get @space_xs + pub fn space_xs(&self) -> u16 { + self.spacing.space_xs + } + /// get @space_s + pub fn space_s(&self) -> u16 { + self.spacing.space_s + } + /// get @space_m + pub fn space_m(&self) -> u16 { + self.spacing.space_m + } + /// get @space_l + pub fn space_l(&self) -> u16 { + self.spacing.space_l + } + /// get @space_xl + pub fn space_xl(&self) -> u16 { + self.spacing.space_xl + } + /// get @space_xxl + pub fn space_xxl(&self) -> u16 { + self.spacing.space_xxl + } + /// get @space_xxxl + pub fn space_xxxl(&self) -> u16 { + self.spacing.space_xxxl + } + + /// get @radius_0 + pub fn radius_0(&self) -> [u16; 4] { + self.corner_radii.radius_0 + } + /// get @radius_xs + pub fn radius_xs(&self) -> [u16; 4] { + self.corner_radii.radius_xs + } + /// get @radius_s + pub fn radius_s(&self) -> [u16; 4] { + self.corner_radii.radius_s + } + /// get @radius_m + pub fn radius_m(&self) -> [u16; 4] { + self.corner_radii.radius_m + } + /// get @radius_l + pub fn radius_l(&self) -> [u16; 4] { + self.corner_radii.radius_l + } + /// get @radius_xl + pub fn radius_xl(&self) -> [u16; 4] { + self.corner_radii.radius_xl + } } impl Theme { @@ -383,6 +461,8 @@ impl Theme { palette: self.palette.into(), is_dark: self.is_dark, is_high_contrast: self.is_high_contrast, + corner_radii: self.corner_radii, + spacing: self.spacing, } } } @@ -411,6 +491,143 @@ where }, is_dark, is_high_contrast, + spacing: Spacing::default(), + corner_radii: CornerRadii::default(), } } } + +/// Helper for building customized themes +#[derive(Debug, Serialize, Deserialize)] +pub struct ThemeBuilder { + palette: CosmicPalette, + spacing: Spacing, + corner_radii: CornerRadii, + neutral_tint: Option, + bg_color: Option, + primary_container_bg: Option, + text_tint: Option, + accent: Option, +} + +impl Default for ThemeBuilder { + fn default() -> Self { + Self { + palette: DARK_PALETTE.to_owned().into(), + spacing: Spacing::default(), + corner_radii: CornerRadii::default(), + neutral_tint: Default::default(), + text_tint: Default::default(), + bg_color: Default::default(), + primary_container_bg: Default::default(), + accent: Default::default(), + } + } +} + +impl ThemeBuilder { + /// Get a builder that is initialized with the default dark theme + pub fn dark() -> Self { + Self { + palette: DARK_PALETTE.to_owned().into(), + ..Default::default() + } + } + + /// Get a builder that is initialized with the default light theme + pub fn light() -> Self { + Self { + palette: LIGHT_PALETTE.to_owned().into(), + ..Default::default() + } + } + + /// Get a builder that is initialized with the default dark high contrast theme + pub fn dark_high_contrast() -> Self { + let palette: CosmicPalette = DARK_PALETTE.to_owned().into(); + Self { + palette: CosmicPalette::HighContrastLight(palette.inner()), + ..Default::default() + } + } + + /// Get a builder that is initialized with the default light high contrast theme + pub fn light_high_contrast() -> Self { + let palette: CosmicPalette = LIGHT_PALETTE.to_owned().into(); + Self { + palette: CosmicPalette::HighContrastLight(palette.inner()), + ..Default::default() + } + } + + /// set the spacing of the builder + pub fn spacing(mut self, spacing: Spacing) -> Self { + self.spacing = spacing; + self + } + + /// set the corner_radii of the builder + pub fn corner_radii(mut self, corner_radii: CornerRadii) -> Self { + self.corner_radii = corner_radii; + self + } + + /// apply a neutral tint to the palette + pub fn neutral_tint(mut self, tint: Srgb) -> Self { + self.neutral_tint = Some(tint); + self + } + + /// apply a text tint to the palette + pub fn text_tint(mut self, tint: Srgb) -> Self { + self.text_tint = Some(tint); + self + } + + /// apply a background color to the palette + pub fn bg_color(mut self, c: Srgba) -> Self { + self.bg_color = Some(c); + self + } + + /// apply a primary container background color to the palette + pub fn primary_container_bg(mut self, c: Srgba) -> Self { + self.primary_container_bg = Some(c); + self + } + + /// apply a accent color to the palette + pub fn accent(mut self, c: Srgb) -> Self { + self.accent = Some(c); + self + } + + /// build the theme + pub fn build(self) -> Theme { + let Self { + mut palette, + spacing, + corner_radii, + neutral_tint, + text_tint, + bg_color, + primary_container_bg, + accent, + } = self; + + if let Some(accent) = accent { + palette.as_mut().accent = accent.into(); + } + + // TODO apply the customizations + + if let Some(accent) = accent { + palette.as_mut().accent = accent.into(); + } + + let mut theme: Theme = palette.into(); + theme.spacing = spacing; + theme.corner_radii = corner_radii; + theme + } +}