增加QScheme主题样式的生成与导出。
This commit is contained in:
parent
826e526ba2
commit
86ecb5a258
|
@ -1,7 +1,8 @@
|
|||
use palette::{
|
||||
cam16::{Cam16Jch, Parameters},
|
||||
convert::FromColorUnclamped,
|
||||
Hsl, IsWithinBounds, Srgb,
|
||||
luma::Luma,
|
||||
Hsl, IntoColor, IsWithinBounds, Oklab, Oklch, Srgb,
|
||||
};
|
||||
|
||||
pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
|
||||
|
@ -44,3 +45,43 @@ pub fn map_hsl_to_srgb(origin: &Hsl) -> Srgb {
|
|||
pub fn map_hsl_to_srgb_hex(origin: &Hsl) -> String {
|
||||
format!("{:x}", map_hsl_to_srgb(origin).into_format::<u8>())
|
||||
}
|
||||
|
||||
pub fn map_oklch_to_luma(origin: &Oklch) -> Luma {
|
||||
let lab_color: Oklab = (*origin).into_color();
|
||||
let linear_rgb = Srgb::from_linear(lab_color.into_color());
|
||||
let luma_color: Luma = linear_rgb.into_color();
|
||||
luma_color
|
||||
}
|
||||
|
||||
pub fn map_oklch_to_srgb(origin: &Oklch) -> Srgb {
|
||||
Srgb::from_linear::<f32>((*origin).into_color())
|
||||
}
|
||||
|
||||
pub fn map_oklch_to_srgb_hex(origin: &Oklch) -> String {
|
||||
format!("{:x}", map_oklch_to_srgb(origin).into_format::<u8>())
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! parse_to_oklch {
|
||||
($origin: ident) => {
|
||||
palette::Oklch::from_color(
|
||||
palette::Srgb::from_str($origin)
|
||||
.map_err(|_| crate::errors::ColorError::UnrecogniazedRGB($origin.to_string()))?
|
||||
.into_format::<f32>(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! parse_option_to_oklch {
|
||||
($origin: ident) => {
|
||||
$origin
|
||||
.map(|color| {
|
||||
let rgb = palette::Srgb::from_str(color)
|
||||
.map_err(|_| crate::errors::ColorError::UnrecogniazedRGB(color.to_string()))?
|
||||
.into_format::<f32>();
|
||||
Ok(palette::Oklch::from_color(rgb))
|
||||
})
|
||||
.transpose()?
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ use std::collections::HashMap;
|
|||
|
||||
use material_design_2::MaterialDesign2Scheme;
|
||||
use material_design_3::MaterialDesign3Scheme;
|
||||
use q_style::{QScheme, SchemeSetting};
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
use crate::errors;
|
||||
|
||||
pub mod material_design_2;
|
||||
pub mod material_design_3;
|
||||
pub mod q_style;
|
||||
|
||||
pub trait SchemeExport {
|
||||
fn output_css_variables(&self) -> String;
|
||||
|
@ -57,3 +59,73 @@ pub fn generate_material_design_2_scheme(
|
|||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_q_scheme_automatically(
|
||||
primary_color: &str,
|
||||
danger_color: &str,
|
||||
success_color: &str,
|
||||
warning_color: &str,
|
||||
info_color: &str,
|
||||
fg_color: &str,
|
||||
bg_color: &str,
|
||||
setting: JsValue,
|
||||
) -> Result<JsValue, errors::ColorError> {
|
||||
let settings: SchemeSetting = serde_wasm_bindgen::from_value(setting)
|
||||
.map_err(|_| errors::ColorError::UnableToParseArgument)?;
|
||||
let scheme = QScheme::new(
|
||||
primary_color,
|
||||
danger_color,
|
||||
success_color,
|
||||
warning_color,
|
||||
info_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
settings,
|
||||
)?;
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_q_scheme_manually(
|
||||
primary_color: &str,
|
||||
secondary_color: Option<String>,
|
||||
tertiary_color: Option<String>,
|
||||
accent_color: Option<String>,
|
||||
danger_color: &str,
|
||||
success_color: &str,
|
||||
warning_color: &str,
|
||||
info_color: &str,
|
||||
fg_color: &str,
|
||||
bg_color: &str,
|
||||
setting: JsValue,
|
||||
) -> Result<JsValue, errors::ColorError> {
|
||||
let settings: SchemeSetting = serde_wasm_bindgen::from_value(setting)
|
||||
.map_err(|_| errors::ColorError::UnableToParseArgument)?;
|
||||
let scheme = QScheme::custom(
|
||||
primary_color,
|
||||
secondary_color.as_deref(),
|
||||
tertiary_color.as_deref(),
|
||||
accent_color.as_deref(),
|
||||
danger_color,
|
||||
success_color,
|
||||
warning_color,
|
||||
info_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
settings,
|
||||
)?;
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
|
241
color-module/src/schemes/q_style/baseline.rs
Normal file
241
color-module/src/schemes/q_style/baseline.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
use palette::{
|
||||
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
|
||||
Oklch, ShiftHue,
|
||||
};
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
use crate::convert::map_oklch_to_srgb_hex;
|
||||
|
||||
use super::{
|
||||
color_set::ColorSet,
|
||||
neutral_swatch::NeutralSwatch,
|
||||
scheme_setting::{ColorExpand, SchemeSetting},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Baseline {
|
||||
pub primary: ColorSet,
|
||||
pub secondary: Option<ColorSet>,
|
||||
pub tertiary: Option<ColorSet>,
|
||||
pub accent: Option<ColorSet>,
|
||||
pub neutral: ColorSet,
|
||||
pub danger: ColorSet,
|
||||
pub success: ColorSet,
|
||||
pub warning: ColorSet,
|
||||
pub info: ColorSet,
|
||||
pub outline: ColorSet,
|
||||
pub foreground: Oklch,
|
||||
pub background: Oklch,
|
||||
_neutral_swatch: NeutralSwatch,
|
||||
}
|
||||
|
||||
impl Baseline {
|
||||
pub fn custom(
|
||||
primary: &Oklch,
|
||||
secondary: &Option<Oklch>,
|
||||
tertiary: &Option<Oklch>,
|
||||
accent: &Option<Oklch>,
|
||||
danger: &Oklch,
|
||||
success: &Oklch,
|
||||
warning: &Oklch,
|
||||
info: &Oklch,
|
||||
foreground: &Oklch,
|
||||
background: &Oklch,
|
||||
setting: SchemeSetting,
|
||||
) -> Self {
|
||||
let neutral_swatch = NeutralSwatch::new(*foreground, *background);
|
||||
let neutral_color = neutral_swatch.get(primary.l);
|
||||
let outline_color = neutral_swatch.get(background.l * 0.7);
|
||||
|
||||
Self {
|
||||
primary: ColorSet::new(primary, &neutral_swatch, &setting),
|
||||
secondary: secondary.map(|color| ColorSet::new(&color, &neutral_swatch, &setting)),
|
||||
tertiary: tertiary.map(|color| ColorSet::new(&color, &neutral_swatch, &setting)),
|
||||
accent: accent.map(|color| ColorSet::new(&color, &neutral_swatch, &setting)),
|
||||
neutral: ColorSet::new(&neutral_color, &neutral_swatch, &setting),
|
||||
danger: ColorSet::new(danger, &neutral_swatch, &setting),
|
||||
success: ColorSet::new(success, &neutral_swatch, &setting),
|
||||
warning: ColorSet::new(warning, &neutral_swatch, &setting),
|
||||
info: ColorSet::new(info, &neutral_swatch, &setting),
|
||||
outline: ColorSet::new(&outline_color, &neutral_swatch, &setting),
|
||||
foreground: *foreground,
|
||||
background: *background,
|
||||
_neutral_swatch: neutral_swatch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
primary: &Oklch,
|
||||
danger: &Oklch,
|
||||
success: &Oklch,
|
||||
warning: &Oklch,
|
||||
info: &Oklch,
|
||||
foreground: &Oklch,
|
||||
background: &Oklch,
|
||||
setting: SchemeSetting,
|
||||
) -> Self {
|
||||
let (secondary, tertiary, accent) = match setting.expand_method {
|
||||
ColorExpand::Complementary => (Some(primary.complementary()), None, None),
|
||||
ColorExpand::Analogous => {
|
||||
let analogous_color = primary.analogous();
|
||||
(Some(analogous_color.0), Some(analogous_color.1), None)
|
||||
}
|
||||
ColorExpand::AnalogousAndComplementary => {
|
||||
let analogous_color = primary.analogous();
|
||||
(
|
||||
Some(analogous_color.0),
|
||||
Some(analogous_color.1),
|
||||
Some(primary.complementary()),
|
||||
)
|
||||
}
|
||||
ColorExpand::Triadic => {
|
||||
let triadic_color = primary.triadic();
|
||||
(Some(triadic_color.0), Some(triadic_color.1), None)
|
||||
}
|
||||
ColorExpand::SplitComplementary => {
|
||||
let split_complementary_color = primary.split_complementary();
|
||||
(
|
||||
Some(split_complementary_color.0),
|
||||
None,
|
||||
Some(split_complementary_color.1),
|
||||
)
|
||||
}
|
||||
ColorExpand::Tetradic => {
|
||||
let tetradic_color = primary.tetradic();
|
||||
(
|
||||
Some(tetradic_color.0),
|
||||
Some(tetradic_color.2),
|
||||
Some(tetradic_color.1),
|
||||
)
|
||||
}
|
||||
ColorExpand::Square => {
|
||||
let c_90 = primary.shift_hue(90.0);
|
||||
let complementary = primary.complementary();
|
||||
let c_270 = primary.shift_hue(270.0);
|
||||
(Some(c_90), Some(c_270), Some(complementary))
|
||||
}
|
||||
};
|
||||
|
||||
Self::custom(
|
||||
primary, &secondary, &tertiary, &accent, danger, success, warning, info, foreground,
|
||||
background, setting,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.primary.to_css_variables(prefix, "primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
variables.extend(secondary.to_css_variables(prefix, "secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
variables.extend(tertiary.to_css_variables(prefix, "tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
variables.extend(accent.to_css_variables(prefix, "accent"));
|
||||
}
|
||||
variables.extend(self.neutral.to_css_variables(prefix, "neutral"));
|
||||
variables.extend(self.danger.to_css_variables(prefix, "danger"));
|
||||
variables.extend(self.success.to_css_variables(prefix, "success"));
|
||||
variables.extend(self.warning.to_css_variables(prefix, "warning"));
|
||||
variables.extend(self.info.to_css_variables(prefix, "info"));
|
||||
variables.extend(self.outline.to_css_variables(prefix, "outline"));
|
||||
variables.push(format!(
|
||||
"--color-{}-foreground: #{};",
|
||||
prefix,
|
||||
map_oklch_to_srgb_hex(&self.foreground)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-background: #{};",
|
||||
prefix,
|
||||
map_oklch_to_srgb_hex(&self.background)
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.primary.to_scss_variables(prefix, "primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
variables.extend(secondary.to_scss_variables(prefix, "secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
variables.extend(tertiary.to_scss_variables(prefix, "tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
variables.extend(accent.to_scss_variables(prefix, "accent"));
|
||||
}
|
||||
variables.extend(self.neutral.to_scss_variables(prefix, "neutral"));
|
||||
variables.extend(self.danger.to_scss_variables(prefix, "danger"));
|
||||
variables.extend(self.success.to_scss_variables(prefix, "success"));
|
||||
variables.extend(self.warning.to_scss_variables(prefix, "warning"));
|
||||
variables.extend(self.info.to_scss_variables(prefix, "info"));
|
||||
variables.extend(self.outline.to_scss_variables(prefix, "outline"));
|
||||
variables.push(format!(
|
||||
"$color-{}-foreground: #{};",
|
||||
prefix,
|
||||
map_oklch_to_srgb_hex(&self.foreground)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-background: #{};",
|
||||
prefix,
|
||||
map_oklch_to_srgb_hex(&self.background)
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_fields(&self) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.primary.to_javascript_fields("primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
variables.extend(secondary.to_javascript_fields("secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
variables.extend(tertiary.to_javascript_fields("tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
variables.extend(accent.to_javascript_fields("accent"));
|
||||
}
|
||||
variables.extend(self.neutral.to_javascript_fields("neutral"));
|
||||
variables.extend(self.danger.to_javascript_fields("danger"));
|
||||
variables.extend(self.success.to_javascript_fields("success"));
|
||||
variables.extend(self.warning.to_javascript_fields("warning"));
|
||||
variables.extend(self.info.to_javascript_fields("info"));
|
||||
variables.extend(self.outline.to_javascript_fields("outline"));
|
||||
variables.push(format!(
|
||||
"foreground: '#{}',",
|
||||
map_oklch_to_srgb_hex(&self.foreground)
|
||||
));
|
||||
variables.push(format!(
|
||||
"background: '#{}',",
|
||||
map_oklch_to_srgb_hex(&self.background)
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Baseline {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_struct("Baseline", 13)?;
|
||||
|
||||
map.serialize_field("primary", &self.primary)?;
|
||||
map.serialize_field("secondary", &self.secondary)?;
|
||||
map.serialize_field("tertiary", &self.tertiary)?;
|
||||
map.serialize_field("accent", &self.accent)?;
|
||||
map.serialize_field("neutral", &self.neutral)?;
|
||||
map.serialize_field("danger", &self.danger)?;
|
||||
map.serialize_field("success", &self.success)?;
|
||||
map.serialize_field("warning", &self.warning)?;
|
||||
map.serialize_field("info", &self.info)?;
|
||||
map.serialize_field("outline", &self.outline)?;
|
||||
map.serialize_field("foreground", &map_oklch_to_srgb_hex(&self.foreground))?;
|
||||
map.serialize_field("background", &map_oklch_to_srgb_hex(&self.background))?;
|
||||
map.end()
|
||||
}
|
||||
}
|
327
color-module/src/schemes/q_style/color_set.rs
Normal file
327
color-module/src/schemes/q_style/color_set.rs
Normal file
|
@ -0,0 +1,327 @@
|
|||
use palette::{color_difference::Wcag21RelativeContrast, luma::Luma, Oklch};
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
use crate::convert::{map_oklch_to_luma, map_oklch_to_srgb_hex};
|
||||
|
||||
use super::{
|
||||
neutral_swatch::NeutralSwatch,
|
||||
scheme_setting::{SchemeSetting, WACGSetting},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ColorSet {
|
||||
pub root: Oklch,
|
||||
pub hover: Oklch,
|
||||
pub active: Oklch,
|
||||
pub focus: Oklch,
|
||||
pub disabled: Oklch,
|
||||
pub on_root: Oklch,
|
||||
pub on_hover: Oklch,
|
||||
pub on_active: Oklch,
|
||||
pub on_focus: Oklch,
|
||||
pub on_disabled: Oklch,
|
||||
}
|
||||
|
||||
fn fit_to_wacg(reference: &Oklch, neutral_swatch: &NeutralSwatch, ratio: f32) -> Oklch {
|
||||
let reference_luma = map_oklch_to_luma(reference);
|
||||
let mut new_target = neutral_swatch.get(reference.l);
|
||||
let quick_factor: f32 = if reference.l <= 0.5 { 0.05 } else { -0.05 };
|
||||
let fine_factor: f32 = if reference.l <= 0.5 { 0.01 } else { -0.01 };
|
||||
|
||||
let match_wacg = |original: &Oklch<f32>, reference: &Luma| {
|
||||
let luma = map_oklch_to_luma(original);
|
||||
luma.relative_contrast(*reference)
|
||||
};
|
||||
|
||||
while match_wacg(&new_target, &reference_luma) < ratio {
|
||||
new_target.l = new_target.l * (1.0 + quick_factor);
|
||||
if new_target.l > 1.0 {
|
||||
new_target.l = 1.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while match_wacg(&new_target, &reference_luma) < ratio {
|
||||
new_target.l = new_target.l * (1.0 + fine_factor);
|
||||
if new_target.l > 1.0 {
|
||||
new_target.l = 1.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
new_target
|
||||
}
|
||||
|
||||
impl ColorSet {
|
||||
pub fn new(color: &Oklch, neutral_swatch: &NeutralSwatch, setting: &SchemeSetting) -> Self {
|
||||
let root = color.clone();
|
||||
let hover = color * setting.hover;
|
||||
let active = color * setting.active;
|
||||
let focus = color * setting.focus;
|
||||
let disabled = color * setting.disabled;
|
||||
|
||||
let (on_root, on_hover, on_active, on_focus, on_disabled) = match setting.wacg_follows {
|
||||
WACGSetting::Fixed => (
|
||||
neutral_swatch.get(root.l),
|
||||
neutral_swatch.get(hover.l),
|
||||
neutral_swatch.get(active.l),
|
||||
neutral_swatch.get(focus.l),
|
||||
neutral_swatch.get(disabled.l),
|
||||
),
|
||||
WACGSetting::AutomaticAA => (
|
||||
fit_to_wacg(&root, neutral_swatch, 4.5),
|
||||
fit_to_wacg(&hover, neutral_swatch, 4.5),
|
||||
fit_to_wacg(&active, neutral_swatch, 4.5),
|
||||
fit_to_wacg(&focus, neutral_swatch, 4.5),
|
||||
fit_to_wacg(&disabled, neutral_swatch, 4.5),
|
||||
),
|
||||
WACGSetting::AutomaticAAA => (
|
||||
fit_to_wacg(&root, neutral_swatch, 7.0),
|
||||
fit_to_wacg(&hover, neutral_swatch, 7.0),
|
||||
fit_to_wacg(&active, neutral_swatch, 7.0),
|
||||
fit_to_wacg(&focus, neutral_swatch, 7.0),
|
||||
fit_to_wacg(&disabled, neutral_swatch, 7.0),
|
||||
),
|
||||
};
|
||||
|
||||
Self {
|
||||
root,
|
||||
hover,
|
||||
active,
|
||||
focus,
|
||||
disabled,
|
||||
on_root,
|
||||
on_hover,
|
||||
on_active,
|
||||
on_focus,
|
||||
on_disabled,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.push(format!(
|
||||
"--color-{}-{}: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.root)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-{}-hover: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.hover)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-{}-active: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.active)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-{}-focus: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.focus)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-{}-disabled: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.disabled)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-on-{}: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_root)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-on-{}-hover: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_hover)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-on-{}-active: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_active)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-on-{}-focus: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_focus)
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{}-on-{}-disabled: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_disabled)
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.push(format!(
|
||||
"$color-{}-{}: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.root)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-{}-hover: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.hover)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-{}-active: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.active)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-{}-focus: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.focus)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-{}-disabled: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.disabled)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-on-{}: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_root)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-on-{}-hover: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_hover)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-on-{}-active: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_active)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-on-{}-focus: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_focus)
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{}-on-{}-disabled: #{};",
|
||||
prefix,
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.on_disabled)
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_fields(&self, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
let capitalized_name = name
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.to_ascii_uppercase()
|
||||
.to_string()
|
||||
+ &name[1..];
|
||||
|
||||
variables.push(format!(
|
||||
"{}: '#{}',",
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.root)
|
||||
));
|
||||
variables.push(format!(
|
||||
"{}Hover: '#{}',",
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.hover)
|
||||
));
|
||||
variables.push(format!(
|
||||
"{}Active: '#{}',",
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.active)
|
||||
));
|
||||
variables.push(format!(
|
||||
"{}Focus: '#{}',",
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.focus)
|
||||
));
|
||||
variables.push(format!(
|
||||
"{}Disabled: '#{}',",
|
||||
name,
|
||||
map_oklch_to_srgb_hex(&self.disabled)
|
||||
));
|
||||
variables.push(format!(
|
||||
"on{}: '#{}',",
|
||||
capitalized_name,
|
||||
map_oklch_to_srgb_hex(&self.on_root)
|
||||
));
|
||||
variables.push(format!(
|
||||
"on{}Hover: '#{}',",
|
||||
capitalized_name,
|
||||
map_oklch_to_srgb_hex(&self.on_hover)
|
||||
));
|
||||
variables.push(format!(
|
||||
"on{}Active: '#{}',",
|
||||
capitalized_name,
|
||||
map_oklch_to_srgb_hex(&self.on_active)
|
||||
));
|
||||
variables.push(format!(
|
||||
"on{}Focus: '#{}',",
|
||||
capitalized_name,
|
||||
map_oklch_to_srgb_hex(&self.on_focus)
|
||||
));
|
||||
variables.push(format!(
|
||||
"on{}Disabled: '#{}',",
|
||||
capitalized_name,
|
||||
map_oklch_to_srgb_hex(&self.on_disabled)
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ColorSet {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let root = map_oklch_to_srgb_hex(&self.root);
|
||||
let hover = map_oklch_to_srgb_hex(&self.hover);
|
||||
let active = map_oklch_to_srgb_hex(&self.active);
|
||||
let focus = map_oklch_to_srgb_hex(&self.focus);
|
||||
let disabled = map_oklch_to_srgb_hex(&self.disabled);
|
||||
let on_root = map_oklch_to_srgb_hex(&self.on_root);
|
||||
let on_hover = map_oklch_to_srgb_hex(&self.on_hover);
|
||||
let on_active = map_oklch_to_srgb_hex(&self.on_active);
|
||||
let on_focus = map_oklch_to_srgb_hex(&self.on_focus);
|
||||
let on_disabled = map_oklch_to_srgb_hex(&self.on_disabled);
|
||||
|
||||
let mut state = serializer.serialize_struct("ColorSet", 10)?;
|
||||
state.serialize_field("root", &root)?;
|
||||
state.serialize_field("hover", &hover)?;
|
||||
state.serialize_field("active", &active)?;
|
||||
state.serialize_field("focus", &focus)?;
|
||||
state.serialize_field("disabled", &disabled)?;
|
||||
state.serialize_field("onRoot", &on_root)?;
|
||||
state.serialize_field("onHover", &on_hover)?;
|
||||
state.serialize_field("onActive", &on_active)?;
|
||||
state.serialize_field("onFocus", &on_focus)?;
|
||||
state.serialize_field("onDisabled", &on_disabled)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
196
color-module/src/schemes/q_style/mod.rs
Normal file
196
color-module/src/schemes/q_style/mod.rs
Normal file
|
@ -0,0 +1,196 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use baseline::Baseline;
|
||||
use palette::FromColor;
|
||||
use scheme_setting::{ColorExpand, WACGSetting};
|
||||
use serde::Serialize;
|
||||
use strum::IntoEnumIterator;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
use crate::{errors, parse_option_to_oklch, parse_to_oklch};
|
||||
|
||||
use super::SchemeExport;
|
||||
|
||||
mod baseline;
|
||||
mod color_set;
|
||||
mod neutral_swatch;
|
||||
mod scheme_setting;
|
||||
|
||||
pub use scheme_setting::SchemeSetting;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct QScheme {
|
||||
pub light: Baseline,
|
||||
pub dark: Baseline,
|
||||
}
|
||||
|
||||
impl QScheme {
|
||||
pub fn new(
|
||||
primary: &str,
|
||||
danger: &str,
|
||||
success: &str,
|
||||
warning: &str,
|
||||
info: &str,
|
||||
foreground: &str,
|
||||
background: &str,
|
||||
setting: SchemeSetting,
|
||||
) -> Result<Self, errors::ColorError> {
|
||||
let primary = parse_to_oklch!(primary);
|
||||
let danger = parse_to_oklch!(danger);
|
||||
let success = parse_to_oklch!(success);
|
||||
let warning = parse_to_oklch!(warning);
|
||||
let info = parse_to_oklch!(info);
|
||||
let foreground = parse_to_oklch!(foreground);
|
||||
let background = parse_to_oklch!(background);
|
||||
Ok(Self {
|
||||
light: Baseline::new(
|
||||
&primary,
|
||||
&danger,
|
||||
&success,
|
||||
&warning,
|
||||
&info,
|
||||
&foreground,
|
||||
&background,
|
||||
setting.clone(),
|
||||
),
|
||||
dark: Baseline::new(
|
||||
&(primary * setting.dark_convert),
|
||||
&(danger * setting.dark_convert),
|
||||
&(success * setting.dark_convert),
|
||||
&(warning * setting.dark_convert),
|
||||
&(info * setting.dark_convert),
|
||||
&(foreground * setting.dark_convert),
|
||||
&(background * setting.dark_convert),
|
||||
setting.clone(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn custom(
|
||||
primary: &str,
|
||||
secondary: Option<&str>,
|
||||
tertiary: Option<&str>,
|
||||
accent: Option<&str>,
|
||||
danger: &str,
|
||||
success: &str,
|
||||
warning: &str,
|
||||
info: &str,
|
||||
foreground: &str,
|
||||
background: &str,
|
||||
setting: SchemeSetting,
|
||||
) -> Result<Self, errors::ColorError> {
|
||||
let primary = parse_to_oklch!(primary);
|
||||
let secondary = parse_option_to_oklch!(secondary);
|
||||
let tertiary = parse_option_to_oklch!(tertiary);
|
||||
let accent = parse_option_to_oklch!(accent);
|
||||
let danger = parse_to_oklch!(danger);
|
||||
let success = parse_to_oklch!(success);
|
||||
let warning = parse_to_oklch!(warning);
|
||||
let info = parse_to_oklch!(info);
|
||||
let foreground = parse_to_oklch!(foreground);
|
||||
let background = parse_to_oklch!(background);
|
||||
|
||||
Ok(Self {
|
||||
light: Baseline::custom(
|
||||
&primary,
|
||||
&secondary,
|
||||
&tertiary,
|
||||
&accent,
|
||||
&danger,
|
||||
&success,
|
||||
&warning,
|
||||
&info,
|
||||
&foreground,
|
||||
&background,
|
||||
setting.clone(),
|
||||
),
|
||||
dark: Baseline::custom(
|
||||
&(primary * setting.dark_convert),
|
||||
&secondary.map(|color| color * setting.dark_convert),
|
||||
&tertiary.map(|color| color * setting.dark_convert),
|
||||
&accent.map(|color| color * setting.dark_convert),
|
||||
&(danger * setting.dark_convert),
|
||||
&(success * setting.dark_convert),
|
||||
&(warning * setting.dark_convert),
|
||||
&(info * setting.dark_convert),
|
||||
&(foreground * setting.dark_convert),
|
||||
&(background * setting.dark_convert),
|
||||
setting.clone(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeExport for QScheme {
|
||||
fn output_css_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.light.to_css_variables("light"));
|
||||
variables.extend(self.dark.to_css_variables("dark"));
|
||||
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.light.to_scss_variables("light"));
|
||||
variables.extend(self.dark.to_scss_variables("dark"));
|
||||
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_javascript_object(&self) -> String {
|
||||
let mut javascript_object = Vec::new();
|
||||
|
||||
javascript_object.push("{".to_string());
|
||||
javascript_object.push(" light: {".to_string());
|
||||
javascript_object.extend(
|
||||
self.light
|
||||
.to_javascript_fields()
|
||||
.into_iter()
|
||||
.map(|c| format!(" {}", c))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
javascript_object.push(" },".to_string());
|
||||
javascript_object.push(" dark: {".to_string());
|
||||
javascript_object.extend(
|
||||
self.dark
|
||||
.to_javascript_fields()
|
||||
.into_iter()
|
||||
.map(|c| format!(" {}", c))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
javascript_object.push("}".to_string());
|
||||
|
||||
javascript_object.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn q_scheme_color_expanding_methods() -> Result<JsValue, String> {
|
||||
let methods = ColorExpand::iter()
|
||||
.map(|variant| {
|
||||
serde_json::json!({
|
||||
"label": variant.label(),
|
||||
"value": variant.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
serde_wasm_bindgen::to_value(&methods).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn q_scheme_wacg_settings() -> Result<JsValue, String> {
|
||||
let settings = WACGSetting::iter()
|
||||
.map(|setting| {
|
||||
serde_json::json!({
|
||||
"label": setting.label(),
|
||||
"value": setting.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
|
||||
}
|
58
color-module/src/schemes/q_style/neutral_swatch.rs
Normal file
58
color-module/src/schemes/q_style/neutral_swatch.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
use palette::Oklch;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NeutralSwatch(Oklch, Oklch);
|
||||
|
||||
impl NeutralSwatch {
|
||||
pub fn new(color1: Oklch, color2: Oklch) -> Self {
|
||||
if color1.l < color2.l {
|
||||
NeutralSwatch(
|
||||
Oklch {
|
||||
l: color1.l * 0.4,
|
||||
chroma: color1.chroma * 0.1,
|
||||
..color1
|
||||
},
|
||||
Oklch {
|
||||
l: color2.l * 1.6,
|
||||
chroma: color2.chroma * 0.1,
|
||||
..color2
|
||||
},
|
||||
)
|
||||
} else {
|
||||
NeutralSwatch(
|
||||
Oklch {
|
||||
l: color2.l * 0.4,
|
||||
chroma: color2.chroma * 0.1,
|
||||
..color2
|
||||
},
|
||||
Oklch {
|
||||
l: color1.l * 1.6,
|
||||
chroma: color1.chroma * 0.1,
|
||||
..color1
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, percent: f32) -> Oklch {
|
||||
let start_hue = self.0.hue.into_positive_degrees();
|
||||
let end_hue = self.1.hue.into_positive_degrees();
|
||||
|
||||
let hue = if (start_hue - end_hue).abs() > 180.0 {
|
||||
if end_hue > start_hue {
|
||||
start_hue + (end_hue + 360.0 - start_hue) * percent
|
||||
} else {
|
||||
start_hue + (end_hue - start_hue + 360.0) * percent
|
||||
}
|
||||
} else {
|
||||
start_hue + (end_hue - start_hue) * percent
|
||||
}
|
||||
.rem_euclid(360.0);
|
||||
|
||||
Oklch {
|
||||
l: percent,
|
||||
chroma: self.0.chroma + (self.1.chroma - self.0.chroma) * percent,
|
||||
hue: hue.into(),
|
||||
}
|
||||
}
|
||||
}
|
139
color-module/src/schemes/q_style/scheme_setting.rs
Normal file
139
color-module/src/schemes/q_style/scheme_setting.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
use std::ops::Mul;
|
||||
|
||||
use palette::Oklch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumIter, EnumString};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct ColorShifting {
|
||||
pub chroma: f32,
|
||||
pub lightness: f32,
|
||||
}
|
||||
|
||||
impl Mul<ColorShifting> for Oklch<f32> {
|
||||
type Output = Oklch<f32>;
|
||||
|
||||
fn mul(self, rhs: ColorShifting) -> Self::Output {
|
||||
Oklch::new(
|
||||
self.l
|
||||
+ if rhs.lightness > 0.0 {
|
||||
(1.0 - self.l) * rhs.lightness
|
||||
} else {
|
||||
self.l * rhs.lightness * -1.0
|
||||
},
|
||||
self.chroma
|
||||
+ if rhs.chroma > 0.0 {
|
||||
(100.0 - self.chroma) * rhs.chroma
|
||||
} else {
|
||||
-(self.chroma * rhs.chroma)
|
||||
},
|
||||
self.hue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<ColorShifting> for &Oklch<f32> {
|
||||
type Output = Oklch<f32>;
|
||||
|
||||
fn mul(self, rhs: ColorShifting) -> Self::Output {
|
||||
Oklch::new(
|
||||
self.l
|
||||
+ if rhs.lightness > 0.0 {
|
||||
(1.0 - self.l) * rhs.lightness
|
||||
} else {
|
||||
self.l * rhs.lightness * -1.0
|
||||
},
|
||||
self.chroma
|
||||
+ if rhs.chroma > 0.0 {
|
||||
(100.0 - self.chroma) * rhs.chroma
|
||||
} else {
|
||||
-(self.chroma * rhs.chroma)
|
||||
},
|
||||
self.hue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub struct SchemeSetting {
|
||||
pub hover: ColorShifting,
|
||||
pub active: ColorShifting,
|
||||
pub focus: ColorShifting,
|
||||
pub disabled: ColorShifting,
|
||||
pub dark_convert: ColorShifting,
|
||||
pub expand_method: ColorExpand,
|
||||
pub wacg_follows: WACGSetting,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Display, EnumString, EnumIter, Serialize, Deserialize)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ColorExpand {
|
||||
Complementary,
|
||||
Analogous,
|
||||
AnalogousAndComplementary,
|
||||
Triadic,
|
||||
SplitComplementary,
|
||||
Tetradic,
|
||||
Square,
|
||||
}
|
||||
|
||||
impl ColorExpand {
|
||||
pub fn label(&self) -> String {
|
||||
match self {
|
||||
ColorExpand::Complementary => "Complementary".to_string(),
|
||||
ColorExpand::Analogous => "Analogous".to_string(),
|
||||
ColorExpand::AnalogousAndComplementary => "Analogous and Complementary".to_string(),
|
||||
ColorExpand::Triadic => "Triadic".to_string(),
|
||||
ColorExpand::SplitComplementary => "Split Complementary".to_string(),
|
||||
ColorExpand::Tetradic => "Tetradic".to_string(),
|
||||
ColorExpand::Square => "Square".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Display, EnumString, EnumIter, Serialize, Deserialize)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum WACGSetting {
|
||||
Fixed,
|
||||
AutomaticAA,
|
||||
AutomaticAAA,
|
||||
}
|
||||
|
||||
impl WACGSetting {
|
||||
pub fn label(&self) -> String {
|
||||
match self {
|
||||
WACGSetting::Fixed => "Fixed".to_string(),
|
||||
WACGSetting::AutomaticAA => "Automatic AA".to_string(),
|
||||
WACGSetting::AutomaticAAA => "Automatic AAA".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SchemeSetting {
|
||||
fn default() -> Self {
|
||||
SchemeSetting {
|
||||
hover: ColorShifting {
|
||||
chroma: 0.0,
|
||||
lightness: 0.3,
|
||||
},
|
||||
active: ColorShifting {
|
||||
chroma: 0.0,
|
||||
lightness: -0.2,
|
||||
},
|
||||
focus: ColorShifting {
|
||||
chroma: 0.0,
|
||||
lightness: 0.5,
|
||||
},
|
||||
disabled: ColorShifting {
|
||||
chroma: -0.9,
|
||||
lightness: -0.2,
|
||||
},
|
||||
dark_convert: ColorShifting {
|
||||
chroma: -0.3,
|
||||
lightness: -0.3,
|
||||
},
|
||||
expand_method: ColorExpand::AnalogousAndComplementary,
|
||||
wacg_follows: WACGSetting::AutomaticAAA,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user