feat: add ThemeBuilder

This commit is contained in:
Ashley Wulber 2023-08-03 16:23:24 -04:00 committed by Ashley Wulber
parent 620c1adb74
commit 607883e4ad
8 changed files with 354 additions and 15 deletions

View file

@ -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],
}
}
}

View file

@ -35,6 +35,29 @@ pub enum CosmicPalette<C> {
HighContrastDark(CosmicPaletteInner<C>),
}
impl<C> CosmicPalette<C> {
/// extract the inner palette
pub fn inner(self) -> CosmicPaletteInner<C> {
match self {
CosmicPalette::Dark(p) => p,
CosmicPalette::Light(p) => p,
CosmicPalette::HighContrastLight(p) => p,
CosmicPalette::HighContrastDark(p) => p,
}
}
}
impl<C> AsMut<CosmicPaletteInner<C>> for CosmicPalette<C> {
fn as_mut(&mut self) -> &mut CosmicPaletteInner<C> {
match self {
CosmicPalette::Dark(p) => p,
CosmicPalette::Light(p) => p,
CosmicPalette::HighContrastLight(p) => p,
CosmicPalette::HighContrastDark(p) => p,
}
}
}
impl<C> AsRef<CosmicPaletteInner<C>> for CosmicPalette<C>
where
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
@ -85,6 +108,9 @@ pub struct CosmicPaletteInner<C> {
/// 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<CosmicPaletteInner<CssColor>> for CosmicPaletteInner<Srgba> {
fn from(p: CosmicPaletteInner<CssColor>) -> 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<CosmicPalette<Srgba>> for CosmicPalette<CssColor> {
fn into(self) -> CosmicPalette<Srgba> {
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()),
}
}
}

View file

@ -1,6 +1,9 @@
Dark (
(
name: "cosmic-dark",
accent: (
c: "#94EBEB",
),
blue: (
c: "#94EBEB",
),

View file

@ -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,
)

View file

@ -1,6 +1,9 @@
Light (
(
name: "cosmic-light",
accent: (
c: "#00496D",
),
blue: (
c: "#00496D",
),

View file

@ -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;

View file

@ -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,
}
}
}

View file

@ -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<C> {
pub warning: Component<C>,
/// palette
pub palette: CosmicPaletteInner<C>,
/// 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<CssColor> {
Ok(is_high_contrast) => default.is_high_contrast = is_high_contrast,
Err(e) => errors.push(e),
}
match config.get::<Spacing>("spacing") {
Ok(spacing) => default.spacing = spacing,
Err(e) => errors.push(e),
}
match config.get::<CornerRadii>("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<CssColor> {
@ -383,6 +461,8 @@ impl Theme<CssColor> {
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<Srgba>,
spacing: Spacing,
corner_radii: CornerRadii,
neutral_tint: Option<Srgb>,
bg_color: Option<Srgba>,
primary_container_bg: Option<Srgba>,
text_tint: Option<Srgb>,
accent: Option<Srgb>,
}
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<Srgba> = 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<Srgba> = 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<Srgba> {
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<Srgba> = palette.into();
theme.spacing = spacing;
theme.corner_radii = corner_radii;
theme
}
}