Compare commits
148 Commits
9606106c45
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
3f2e655b1c | ||
|
ae37903673 | ||
|
d88eebe356 | ||
|
f6ff4b10e6 | ||
|
1e4a9fd858 | ||
|
034f12c99b | ||
|
61ff3eff5c | ||
|
3bed5a97c5 | ||
|
ab4af06fd1 | ||
|
ec93cd5678 | ||
|
38b11dcd85 | ||
|
0d9c11b4fb | ||
|
d6fba55f3d | ||
|
58be84c499 | ||
|
5a1454e6c2 | ||
|
dd1273dad4 | ||
|
afaa7d25de | ||
|
edc2a0546e | ||
|
f82575c49b | ||
|
a77fb3f18b | ||
|
a7ef8eb576 | ||
|
600c8c92ce | ||
|
137079e5c6 | ||
|
a71a635eb8 | ||
|
8a09806b8c | ||
|
459b5ea1ab | ||
|
4119a1ab64 | ||
|
e327885545 | ||
|
680ca173da | ||
|
622b76a621 | ||
|
bd4a2c9b49 | ||
|
2bbb46ced1 | ||
|
199bd8c3e5 | ||
|
ddfc2fff15 | ||
|
ba8991d1b5 | ||
|
25a3cf0fce | ||
|
56ba55a4ca | ||
|
036b9fead6 | ||
|
1db89e57cc | ||
|
367117d8aa | ||
|
e32eed405f | ||
|
6643eae433 | ||
|
cb9a01109e | ||
|
efb2237135 | ||
|
2638bbd99a | ||
|
f284a7ef62 | ||
|
00d1e425c0 | ||
|
0a5d475655 | ||
|
a1f63cd724 | ||
|
5f3d58f0f5 | ||
|
ef3ef2b349 | ||
|
3b0600e64a | ||
|
21b538af99 | ||
|
e170e3c11d | ||
|
c4a2f6f638 | ||
|
dfbbe2b884 | ||
|
7fba372d08 | ||
|
f3f259fd1c | ||
|
55f0eab76d | ||
|
ed2e323e3e | ||
|
1a136670ff | ||
|
639b6b223e | ||
|
e980eeec3d | ||
|
4b56d3a625 | ||
|
84c164b2c8 | ||
|
9b6f4ace14 | ||
|
4054c2ce56 | ||
|
d0e8acc5c0 | ||
|
e2f78aefb3 | ||
|
6396b257cb | ||
|
cd2d724b52 | ||
|
715459eef0 | ||
|
31cedd2547 | ||
|
7f6c217d7e | ||
|
ccedaa62a6 | ||
|
128eeb24ac | ||
|
2ddffe1b12 | ||
|
3eae8116e7 | ||
|
12e76b658e | ||
|
2ec3578e1c | ||
|
f944d48e1b | ||
|
88e3d1f928 | ||
|
2144cd548a | ||
|
2c47369772 | ||
|
2f51a80c91 | ||
|
546ca97b10 | ||
|
6b262f536d | ||
|
6aa3875919 | ||
|
0369f238f2 | ||
|
c60aefaaff | ||
|
7bfe9a7620 | ||
|
1b044c66d7 | ||
|
e74ffc9721 | ||
|
1553c51621 | ||
|
14d775e956 | ||
|
e2806a0cc5 | ||
|
5d3fc2903b | ||
|
71feeb4efc | ||
|
2b9547a7c2 | ||
|
131c43c5cf | ||
|
505af1c67e | ||
|
320b750834 | ||
|
6728ca1be2 | ||
|
83dcb3f80f | ||
|
b8018e323d | ||
|
d68ac6a3df | ||
|
0f5805bb7f | ||
|
a3fb9b656b | ||
|
d817024bf3 | ||
|
d98e3a69d9 | ||
|
8b0e9699c7 | ||
|
8e71d3c555 | ||
|
e9c2d4cb16 | ||
|
2acb69da20 | ||
|
9664983b5c | ||
|
fc340f3f74 | ||
|
f9f855e818 | ||
|
853b9b6b75 | ||
|
41788c4944 | ||
|
2bc250fc3d | ||
|
7468e28928 | ||
|
ca83ce082b | ||
|
89b2a2f9d9 | ||
|
74dd9e7354 | ||
|
592244911f | ||
|
08fabb53a2 | ||
|
0350380df6 | ||
|
32d8457802 | ||
|
b124bb4eda | ||
|
92229b0de4 | ||
|
e3642cad97 | ||
|
59519e1408 | ||
|
56a4786675 | ||
|
838f0c0fa0 | ||
|
9fa05824d2 | ||
|
14851c8284 | ||
|
50646ffccf | ||
|
a9ad4dea5d | ||
|
7b26c95a9a | ||
|
8efb3ec318 | ||
|
c4f703906e | ||
|
6dba92a2c5 | ||
|
4b4428fd3b | ||
|
1b41fb4d22 | ||
|
a3de0f961a | ||
|
79794ed0f7 | ||
|
20757a789a | ||
|
f9f984a1b4 |
@@ -1,21 +1,29 @@
|
||||
[package]
|
||||
name = "color-module"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version = "0.1.9"
|
||||
edition = "2024"
|
||||
rust-version = "1.88.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
color-name = "1.1.0"
|
||||
enum-iterator = "2.1.0"
|
||||
getrandom = { version = "0.3.3", features = ["wasm_js"] }
|
||||
internment = { version = "0.8.6", features = ["arc"] }
|
||||
linked-hash-map = { version = "0.5.6", features = ["serde", "serde_impl"] }
|
||||
linked_hash_set = { version = "0.1.5", features = ["serde"] }
|
||||
palette = { version = "0.7.6", features = ["serde"] }
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde-wasm-bindgen = "0.6.5"
|
||||
serde_json = "1.0.134"
|
||||
strum = { version = "0.26.3", features = ["derive", "strum_macros"] }
|
||||
strum_macros = "0.26.4"
|
||||
thiserror = "2.0.9"
|
||||
serde_json = "1.0.140"
|
||||
serde_repr = "0.1.20"
|
||||
strum = { version = "0.27.1", features = ["derive", "strum_macros"] }
|
||||
strum_macros = "0.27.1"
|
||||
thiserror = "2.0.12"
|
||||
wasm-bindgen = { version = "0.2.99", features = ["serde", "serde_json", "serde-serialize"] }
|
||||
web-sys = {version = "0.3.77", features = ["console", "Window"]}
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.49"
|
||||
|
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
wasm-pack build --release --target web -d ../src/color_functions
|
||||
RUSTFLAGS='--cfg getrandom_backend="wasm_js"' wasm-pack build --release --target web -d ../color_functions
|
||||
|
@@ -12,6 +12,18 @@ pub struct HctDiffference {
|
||||
pub lightness: Differ,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl HctDiffference {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(hue: Differ, chroma: Differ, lightness: Differ) -> Self {
|
||||
Self {
|
||||
hue,
|
||||
chroma,
|
||||
lightness,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDifference for Cam16Jch<f32> {
|
||||
type Difference = HctDiffference;
|
||||
|
||||
|
@@ -12,6 +12,18 @@ pub struct HSLDifference {
|
||||
pub lightness: Differ,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl HSLDifference {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(hue: Differ, saturation: Differ, lightness: Differ) -> Self {
|
||||
Self {
|
||||
hue,
|
||||
saturation,
|
||||
lightness,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDifference for Hsl {
|
||||
type Difference = HSLDifference;
|
||||
|
||||
|
@@ -13,6 +13,14 @@ pub struct Differ {
|
||||
pub percent: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Differ {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(delta: f32, percent: f32) -> Self {
|
||||
Self { delta, percent }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ColorDifference {
|
||||
type Difference;
|
||||
|
||||
|
@@ -12,6 +12,18 @@ pub struct OklchDifference {
|
||||
pub lightness: Differ,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl OklchDifference {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(hue: Differ, chroma: Differ, lightness: Differ) -> Self {
|
||||
Self {
|
||||
hue,
|
||||
chroma,
|
||||
lightness,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDifference for Oklch {
|
||||
type Difference = OklchDifference;
|
||||
|
||||
|
@@ -12,6 +12,14 @@ pub struct RGBDifference {
|
||||
pub b: Differ,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl RGBDifference {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(r: Differ, g: Differ, b: Differ) -> Self {
|
||||
Self { r, g, b }
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDifference for Srgb {
|
||||
type Difference = RGBDifference;
|
||||
|
||||
|
@@ -2,30 +2,63 @@ use palette::{
|
||||
cam16::{Cam16Jch, Parameters},
|
||||
convert::FromColorUnclamped,
|
||||
luma::Luma,
|
||||
Hsl, IntoColor, IsWithinBounds, Oklab, Oklch, Srgb,
|
||||
Hsl, IntoColor, IsWithinBounds, Lch, Lchuv, Oklab, Oklch, Srgb,
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn map_cam16jch_to_srgb(origin: &Cam16Jch<f32>) -> Srgb {
|
||||
let mut new_original = Cam16Jch::new(origin.lightness, origin.chroma, origin.hue);
|
||||
const FACTOR: f32 = 0.99;
|
||||
let original_xyz = origin.into_xyz(Parameters::default_static_wp(40.0));
|
||||
let mut new_srgb = Srgb::from_color_unclamped(original_xyz);
|
||||
if new_srgb.is_within_bounds() {
|
||||
return new_srgb;
|
||||
}
|
||||
|
||||
let lchuv: Lchuv = Lchuv::from_color_unclamped(original_xyz);
|
||||
let mut c: f32 = lchuv.chroma;
|
||||
let original_c = c;
|
||||
let h = lchuv.hue;
|
||||
let l = lchuv.l;
|
||||
|
||||
loop {
|
||||
let new_srgb =
|
||||
Srgb::from_color_unclamped(new_original.into_xyz(Parameters::default_static_wp(40.0)));
|
||||
if new_srgb.is_within_bounds() {
|
||||
let new_lchuv = Lchuv::new(l, c, h);
|
||||
new_srgb = new_lchuv.into_color();
|
||||
c -= original_c / 1000.0;
|
||||
if c > 0.0 && (new_srgb.is_within_bounds()) {
|
||||
break new_srgb;
|
||||
}
|
||||
new_original = Cam16Jch::new(
|
||||
new_original.lightness,
|
||||
new_original.chroma * FACTOR,
|
||||
new_original.hue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn map_cam16jch_to_srgb_hex(origin: &Cam16Jch<f32>) -> String {
|
||||
format!("{:x}", map_cam16jch_to_srgb(origin).into_format::<u8>())
|
||||
}
|
||||
|
||||
pub fn map_lch_to_srgb(origin: &Lch) -> Srgb {
|
||||
let mut new_srgb: Srgb = (*origin).into_color();
|
||||
if new_srgb.is_within_bounds() {
|
||||
return new_srgb;
|
||||
}
|
||||
|
||||
let mut c: f32 = origin.chroma;
|
||||
let original_c = c;
|
||||
let h = origin.hue;
|
||||
let l = origin.l;
|
||||
|
||||
loop {
|
||||
let new_lch = Lch::new(l, c, h);
|
||||
new_srgb = new_lch.into_color();
|
||||
c -= original_c / 1000.0;
|
||||
if c > 0.0 && (new_srgb.is_within_bounds()) {
|
||||
break new_srgb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_lch_to_srgb_hex(origin: &Lch) -> String {
|
||||
format!("{:x}", map_lch_to_srgb(origin).into_format::<u8>())
|
||||
}
|
||||
|
||||
pub fn map_hsl_to_srgb(origin: &Hsl) -> Srgb {
|
||||
let mut new_original = Hsl::new(origin.hue, origin.saturation, origin.lightness);
|
||||
const FACTOR: f32 = 0.99;
|
||||
|
12
color-module/src/foreign_serializer.rs
Normal file
12
color-module/src/foreign_serializer.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use palette::Oklch;
|
||||
use serde::Serializer;
|
||||
|
||||
use crate::convert::map_oklch_to_srgb_hex;
|
||||
|
||||
pub fn serialize_oklch_to_hex<S>(color: &Oklch, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let hex_color = map_oklch_to_srgb_hex(color);
|
||||
serializer.serialize_str(&hex_color)
|
||||
}
|
@@ -1,10 +1,10 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use palette::{
|
||||
FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb,
|
||||
cam16::{Cam16Jch, Parameters},
|
||||
color_difference::Wcag21RelativeContrast,
|
||||
convert::FromColorUnclamped,
|
||||
FromColor, Hsl, IntoColor, IsWithinBounds, Lab, Oklch, Srgb,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
@@ -14,6 +14,7 @@ mod color_differ;
|
||||
mod color_shifting;
|
||||
mod convert;
|
||||
mod errors;
|
||||
mod foreign_serializer;
|
||||
mod palettes;
|
||||
mod reversing;
|
||||
mod schemes;
|
||||
@@ -135,3 +136,10 @@ pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result<f32, err
|
||||
.into_format::<f32>();
|
||||
Ok(fg_srgb.relative_contrast(bg_srgb))
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cond {
|
||||
($s: expr, $a: expr, $b: expr) => {
|
||||
if $s { $a } else { $b }
|
||||
};
|
||||
}
|
||||
|
@@ -11,6 +11,19 @@ pub struct MixReversing {
|
||||
pub average: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl MixReversing {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(r_factor: f32, g_factor: f32, b_factor: f32, average: f32) -> Self {
|
||||
Self {
|
||||
r_factor,
|
||||
g_factor,
|
||||
b_factor,
|
||||
average,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MixReversing {
|
||||
pub fn from_tint_rgb(basic_color: Rgb, mixed_result: Rgb) -> Self {
|
||||
let r_factor = if basic_color.red == 1.0 {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
use crate::{convert::map_hsl_to_srgb_hex, errors, schemes::material_design_2::swatch::M2Swatch};
|
||||
@@ -45,18 +46,8 @@ impl M2BaselineColors {
|
||||
None,
|
||||
)?,
|
||||
error: M2ColorSet::from_swatch(&error_swatch, &neutral_swatch, dark_baseline, None)?,
|
||||
background: M2ColorSet::from_swatch(
|
||||
&neutral_swatch,
|
||||
&neutral_swatch,
|
||||
dark_baseline,
|
||||
None,
|
||||
)?,
|
||||
surface: M2ColorSet::from_swatch(
|
||||
&neutral_swatch,
|
||||
&neutral_swatch,
|
||||
dark_baseline,
|
||||
None,
|
||||
)?,
|
||||
background: M2ColorSet::surface(&neutral_swatch, dark_baseline)?,
|
||||
surface: M2ColorSet::surface(&neutral_swatch, dark_baseline)?,
|
||||
shadow: map_hsl_to_srgb_hex(&neutral_swatch.tone(SwatchIndex::SI900)),
|
||||
custom_colors: HashMap::new(),
|
||||
neutral_swatch,
|
||||
@@ -85,12 +76,28 @@ impl M2BaselineColors {
|
||||
variable_lines.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
||||
|
||||
for (name, color_set) in &self.custom_colors {
|
||||
variable_lines.extend(color_set.to_css_variable(&prefix, name));
|
||||
variable_lines.extend(color_set.to_css_variable(&prefix, &name.to_lowercase()));
|
||||
}
|
||||
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self) -> LinkedHashMap<String, String> {
|
||||
let mut variables = LinkedHashMap::new();
|
||||
|
||||
variables.extend(self.primary.to_css_auto_scheme_collection("primary"));
|
||||
variables.extend(self.secondary.to_css_auto_scheme_collection("secondary"));
|
||||
variables.extend(self.error.to_css_auto_scheme_collection("error"));
|
||||
variables.extend(self.background.to_css_auto_scheme_collection("background"));
|
||||
variables.extend(self.surface.to_css_auto_scheme_collection("surface"));
|
||||
variables.insert("shadow".to_string(), self.shadow.clone());
|
||||
for (name, color_set) in &self.custom_colors {
|
||||
variables.extend(color_set.to_css_auto_scheme_collection(&name.to_lowercase()));
|
||||
}
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variable(&self) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
@@ -103,7 +110,7 @@ impl M2BaselineColors {
|
||||
variable_lines.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
||||
|
||||
for (name, color_set) in &self.custom_colors {
|
||||
variable_lines.extend(color_set.to_scss_variable(&prefix, name));
|
||||
variable_lines.extend(color_set.to_scss_variable(&prefix, &name.to_lowercase()));
|
||||
}
|
||||
|
||||
variable_lines
|
||||
@@ -121,7 +128,7 @@ impl M2BaselineColors {
|
||||
variable_lines.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
||||
|
||||
for (name, color_set) in &self.custom_colors {
|
||||
variable_lines.extend(color_set.to_javascript_object(&prefix, name));
|
||||
variable_lines.extend(color_set.to_javascript_object(&prefix, &name.to_lowercase()));
|
||||
}
|
||||
|
||||
variable_lines
|
||||
|
@@ -1,3 +1,4 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{convert::map_hsl_to_srgb_hex, errors};
|
||||
@@ -48,6 +49,27 @@ impl M2ColorSet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn surface(neutral: &M2Swatch, dark: bool) -> Result<Self, errors::ColorError> {
|
||||
let root_color_index = if dark {
|
||||
super::swatch::SwatchIndex::SI900
|
||||
} else {
|
||||
super::swatch::SwatchIndex::SI50
|
||||
};
|
||||
if dark {
|
||||
Ok(Self {
|
||||
root: map_hsl_to_srgb_hex(&neutral.desaturated_tone(root_color_index)),
|
||||
variant: map_hsl_to_srgb_hex(&neutral.desaturated_tone(root_color_index + 2)),
|
||||
on: map_hsl_to_srgb_hex(&neutral.tone(super::swatch::SwatchIndex::SI50)),
|
||||
})
|
||||
} else {
|
||||
Ok(Self {
|
||||
root: map_hsl_to_srgb_hex(&neutral.tone(root_color_index)),
|
||||
variant: map_hsl_to_srgb_hex(&neutral.tone(root_color_index - 2)),
|
||||
on: map_hsl_to_srgb_hex(&neutral.tone(super::swatch::SwatchIndex::SI900)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variable(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
|
||||
@@ -61,6 +83,16 @@ impl M2ColorSet {
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut variables = LinkedHashMap::new();
|
||||
|
||||
variables.insert(format!("{}", name), self.root.clone());
|
||||
variables.insert(format!("{}-variant", name), self.variant.clone());
|
||||
variables.insert(format!("on-{}", name), self.on.clone());
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variable(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
use baseline::M2BaselineColors;
|
||||
use linked_hash_set::LinkedHashSet;
|
||||
use palette::Hsl;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -48,6 +49,34 @@ impl SchemeExport for MaterialDesign2Scheme {
|
||||
css_variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_css_auto_scheme_variables(&self) -> String {
|
||||
let mut auto_scheme_variables = Vec::new();
|
||||
let mut keys = LinkedHashSet::new();
|
||||
let light_baseline = self.light.to_css_auto_scheme_collection();
|
||||
let dark_baseline = self.dark.to_css_auto_scheme_collection();
|
||||
|
||||
auto_scheme_variables.push(format!("--color-white: #{};", self.white));
|
||||
auto_scheme_variables.push(format!("--color-black: #{};", self.black));
|
||||
|
||||
keys.extend(light_baseline.keys().cloned());
|
||||
keys.extend(dark_baseline.keys().cloned());
|
||||
for key in keys {
|
||||
match (light_baseline.get(&key), dark_baseline.get(&key)) {
|
||||
(Some(light), Some(dark)) => {
|
||||
auto_scheme_variables.push(format!(
|
||||
"--color-{}: light-dark(#{}, #{});",
|
||||
key, light, dark
|
||||
));
|
||||
}
|
||||
(Some(color), None) | (None, Some(color)) => {
|
||||
auto_scheme_variables.push(format!("--color-{}: #{};", key, color));
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
auto_scheme_variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut scss_variables = Vec::new();
|
||||
|
||||
|
@@ -1,8 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
use crate::convert::map_lch_to_srgb_hex;
|
||||
|
||||
use super::{color_set::M3ColorSet, surface::M3SurfaceSet, tonal_palette::TonalPalette};
|
||||
|
||||
@@ -16,7 +17,7 @@ pub struct M3BaselineColors {
|
||||
pub outline: String,
|
||||
pub outline_variant: String,
|
||||
pub scrim: String,
|
||||
pub shadown: String,
|
||||
pub shadow: String,
|
||||
pub customs: HashMap<String, M3ColorSet>,
|
||||
dark_set: bool,
|
||||
}
|
||||
@@ -62,10 +63,10 @@ impl M3BaselineColors {
|
||||
tertiary,
|
||||
error,
|
||||
surface,
|
||||
outline: map_cam16jch_to_srgb_hex(&outline),
|
||||
outline_variant: map_cam16jch_to_srgb_hex(&outline_variant),
|
||||
scrim: map_cam16jch_to_srgb_hex(&scrim),
|
||||
shadown: map_cam16jch_to_srgb_hex(&shadow),
|
||||
outline: map_lch_to_srgb_hex(&outline),
|
||||
outline_variant: map_lch_to_srgb_hex(&outline_variant),
|
||||
scrim: map_lch_to_srgb_hex(&scrim),
|
||||
shadow: map_lch_to_srgb_hex(&shadow),
|
||||
customs: HashMap::new(),
|
||||
dark_set,
|
||||
}
|
||||
@@ -80,6 +81,34 @@ impl M3BaselineColors {
|
||||
self.customs.insert(name, color_set_generator(c));
|
||||
}
|
||||
|
||||
pub fn full_custom(
|
||||
primary: M3ColorSet,
|
||||
secondary: M3ColorSet,
|
||||
tertiary: M3ColorSet,
|
||||
error: M3ColorSet,
|
||||
surface: M3SurfaceSet,
|
||||
outline: String,
|
||||
outline_variant: String,
|
||||
scrim: String,
|
||||
shadow: String,
|
||||
customs: HashMap<String, M3ColorSet>,
|
||||
dark_set: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
primary,
|
||||
secondary,
|
||||
tertiary,
|
||||
error,
|
||||
surface,
|
||||
outline,
|
||||
outline_variant,
|
||||
scrim,
|
||||
shadow,
|
||||
customs,
|
||||
dark_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
@@ -95,14 +124,33 @@ impl M3BaselineColors {
|
||||
prefix, self.outline_variant
|
||||
));
|
||||
css_variables.push(format!("--color-{}-scrim: #{};", prefix, self.scrim));
|
||||
css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadown));
|
||||
css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
||||
for (name, color_set) in &self.customs {
|
||||
css_variables.extend(color_set.to_css_variables(prefix, name));
|
||||
css_variables.extend(color_set.to_css_variables(prefix, &name.to_lowercase()));
|
||||
}
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self) -> LinkedHashMap<String, String> {
|
||||
let mut collection = LinkedHashMap::new();
|
||||
|
||||
collection.extend(self.primary.to_css_auto_scheme_collection("primary"));
|
||||
collection.extend(self.secondary.to_css_auto_scheme_collection("secondary"));
|
||||
collection.extend(self.tertiary.to_css_auto_scheme_collection("tertiary"));
|
||||
collection.extend(self.error.to_css_auto_scheme_collection("error"));
|
||||
collection.extend(self.surface.to_css_auto_scheme_collection());
|
||||
collection.insert("outline".to_string(), self.outline.clone());
|
||||
collection.insert("outline-variant".to_string(), self.outline_variant.clone());
|
||||
collection.insert("scrim".to_string(), self.scrim.clone());
|
||||
collection.insert("shadow".to_string(), self.shadow.clone());
|
||||
for (name, color_set) in &self.customs {
|
||||
collection.extend(color_set.to_css_auto_scheme_collection(&name.to_lowercase()));
|
||||
}
|
||||
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self) -> Vec<String> {
|
||||
let mut scss_variables = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
@@ -118,9 +166,9 @@ impl M3BaselineColors {
|
||||
prefix, self.outline_variant
|
||||
));
|
||||
scss_variables.push(format!("$color-{}-scrim: #{};", prefix, self.scrim));
|
||||
scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadown));
|
||||
scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
||||
for (name, color_set) in &self.customs {
|
||||
scss_variables.extend(color_set.to_scss_variables(prefix, name));
|
||||
scss_variables.extend(color_set.to_scss_variables(prefix, &name.to_lowercase()));
|
||||
}
|
||||
|
||||
scss_variables
|
||||
@@ -147,9 +195,10 @@ impl M3BaselineColors {
|
||||
prefix, self.outline_variant
|
||||
));
|
||||
js_object_fields.push(format!("{}Scrim: '#{}',", prefix, self.scrim));
|
||||
js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadown));
|
||||
js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
||||
for (name, color_set) in &self.customs {
|
||||
js_object_fields.extend(color_set.to_javascript_object_fields(prefix, name));
|
||||
js_object_fields
|
||||
.extend(color_set.to_javascript_object_fields(prefix, &name.to_lowercase()));
|
||||
}
|
||||
|
||||
js_object_fields
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
use crate::convert::map_lch_to_srgb_hex;
|
||||
|
||||
use super::tonal_palette::TonalPalette;
|
||||
|
||||
@@ -9,7 +10,7 @@ pub struct M3ColorSet {
|
||||
pub root: String,
|
||||
pub on_root: String,
|
||||
pub container: String,
|
||||
pub on_conatiner: String,
|
||||
pub on_container: String,
|
||||
pub fixed: String,
|
||||
pub fixed_dim: String,
|
||||
pub on_fixed: String,
|
||||
@@ -30,15 +31,15 @@ impl M3ColorSet {
|
||||
let inverse = palette.tone(80.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
on_conatiner: map_cam16jch_to_srgb_hex(&on_container),
|
||||
fixed: map_cam16jch_to_srgb_hex(&fixed),
|
||||
fixed_dim: map_cam16jch_to_srgb_hex(&fixed_dim),
|
||||
on_fixed: map_cam16jch_to_srgb_hex(&on_fixed),
|
||||
fixed_variant: map_cam16jch_to_srgb_hex(&fixed_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
root: map_lch_to_srgb_hex(&root),
|
||||
on_root: map_lch_to_srgb_hex(&on_root),
|
||||
container: map_lch_to_srgb_hex(&container),
|
||||
on_container: map_lch_to_srgb_hex(&on_container),
|
||||
fixed: map_lch_to_srgb_hex(&fixed),
|
||||
fixed_dim: map_lch_to_srgb_hex(&fixed_dim),
|
||||
on_fixed: map_lch_to_srgb_hex(&on_fixed),
|
||||
fixed_variant: map_lch_to_srgb_hex(&fixed_variant),
|
||||
inverse: map_lch_to_srgb_hex(&inverse),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,15 +55,15 @@ impl M3ColorSet {
|
||||
let inverse = palette.tone(40.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
on_conatiner: map_cam16jch_to_srgb_hex(&on_container),
|
||||
fixed: map_cam16jch_to_srgb_hex(&fixed),
|
||||
fixed_dim: map_cam16jch_to_srgb_hex(&fixed_dim),
|
||||
on_fixed: map_cam16jch_to_srgb_hex(&on_fixed),
|
||||
fixed_variant: map_cam16jch_to_srgb_hex(&fixed_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
root: map_lch_to_srgb_hex(&root),
|
||||
on_root: map_lch_to_srgb_hex(&on_root),
|
||||
container: map_lch_to_srgb_hex(&container),
|
||||
on_container: map_lch_to_srgb_hex(&on_container),
|
||||
fixed: map_lch_to_srgb_hex(&fixed),
|
||||
fixed_dim: map_lch_to_srgb_hex(&fixed_dim),
|
||||
on_fixed: map_lch_to_srgb_hex(&on_fixed),
|
||||
fixed_variant: map_lch_to_srgb_hex(&fixed_variant),
|
||||
inverse: map_lch_to_srgb_hex(&inverse),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ impl M3ColorSet {
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-on-{}-container: #{};",
|
||||
prefix, name, self.on_conatiner
|
||||
prefix, name, self.on_container
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"--color-{}-{}-fixed: #{};",
|
||||
@@ -106,6 +107,25 @@ impl M3ColorSet {
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut variables = LinkedHashMap::new();
|
||||
|
||||
variables.insert(format!("{}", name), self.root.clone());
|
||||
variables.insert(format!("on-{}", name), self.on_root.clone());
|
||||
variables.insert(format!("{}-container", name), self.container.clone());
|
||||
variables.insert(format!("on-{}-container", name), self.on_container.clone());
|
||||
variables.insert(format!("{}-fixed", name), self.fixed.clone());
|
||||
variables.insert(format!("{}-fixed-dim", name), self.fixed_dim.clone());
|
||||
variables.insert(format!("on-{}-fixed", name), self.on_fixed.clone());
|
||||
variables.insert(
|
||||
format!("on-{}-fixed-variant", name),
|
||||
self.fixed_variant.clone(),
|
||||
);
|
||||
variables.insert(format!("inverse-{}", name), self.inverse.clone());
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
|
||||
@@ -117,7 +137,7 @@ impl M3ColorSet {
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-on-{}-container: #{};",
|
||||
prefix, name, self.on_conatiner
|
||||
prefix, name, self.on_container
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"$color-{}-{}-fixed: #{};",
|
||||
@@ -162,7 +182,7 @@ impl M3ColorSet {
|
||||
));
|
||||
variable_lines.push(format!(
|
||||
"{}On{}Container: '#{}',",
|
||||
prefix, name, self.on_conatiner
|
||||
prefix, name, self.on_container
|
||||
));
|
||||
variable_lines.push(format!("{}{}Fixed: '#{}',", prefix, name, self.fixed));
|
||||
variable_lines.push(format!(
|
||||
|
@@ -1,12 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use baseline::M3BaselineColors;
|
||||
use palette::cam16::{Cam16Jch, Parameters};
|
||||
use palette::{IntoColor, Srgb};
|
||||
pub use baseline::M3BaselineColors;
|
||||
pub use color_set::M3ColorSet;
|
||||
use linked_hash_set::LinkedHashSet;
|
||||
use palette::{IntoColor, Lch, Srgb};
|
||||
use serde::Serialize;
|
||||
use tonal_palette::TonalPalette;
|
||||
pub use surface::M3SurfaceSet;
|
||||
pub use swatch::M3PaletteSwatch;
|
||||
pub use tonal_palette::TonalPalette;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
use crate::convert::map_lch_to_srgb_hex;
|
||||
use crate::errors;
|
||||
|
||||
use super::SchemeExport;
|
||||
@@ -14,6 +18,7 @@ use super::SchemeExport;
|
||||
mod baseline;
|
||||
mod color_set;
|
||||
mod surface;
|
||||
mod swatch;
|
||||
mod tonal_palette;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@@ -22,24 +27,19 @@ pub struct MaterialDesign3Scheme {
|
||||
pub black: String,
|
||||
pub light_baseline: M3BaselineColors,
|
||||
pub dark_baseline: M3BaselineColors,
|
||||
pub swatches: HashMap<String, M3PaletteSwatch>,
|
||||
}
|
||||
|
||||
impl MaterialDesign3Scheme {
|
||||
pub fn new(source_color: &str, error_color: &str) -> Result<Self, errors::ColorError> {
|
||||
let source = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(source_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(source_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let error = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(error_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(error_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let source: Lch = Srgb::from_str(source_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(source_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color();
|
||||
let error: Lch = Srgb::from_str(error_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(error_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color();
|
||||
let source_hue = source.hue.into_positive_degrees();
|
||||
let p = TonalPalette::from_hue_and_chroma(source_hue, source.chroma);
|
||||
let s = TonalPalette::from_hue_and_chroma(source_hue, source.chroma / 3.0);
|
||||
@@ -48,11 +48,20 @@ impl MaterialDesign3Scheme {
|
||||
let nv = TonalPalette::from_hue_and_chroma(source_hue, (source.chroma / 6.0).min(8.0));
|
||||
let e = TonalPalette::from_hue_and_chroma(error.hue.into_positive_degrees(), 84.0);
|
||||
|
||||
let mut swatches = HashMap::new();
|
||||
swatches.insert("primary".to_string(), M3PaletteSwatch::new(&p));
|
||||
swatches.insert("secondary".to_string(), M3PaletteSwatch::new(&s));
|
||||
swatches.insert("tertiary".to_string(), M3PaletteSwatch::new(&t));
|
||||
swatches.insert("error".to_string(), M3PaletteSwatch::new(&e));
|
||||
swatches.insert("neutral".to_string(), M3PaletteSwatch::new(&n));
|
||||
swatches.insert("neutral_variant".to_string(), M3PaletteSwatch::new(&nv));
|
||||
|
||||
Ok(Self {
|
||||
white: map_cam16jch_to_srgb_hex(&Cam16Jch::new(100.0, 0.0, 0.0)),
|
||||
black: map_cam16jch_to_srgb_hex(&Cam16Jch::new(0.0, 0.0, 0.0)),
|
||||
white: map_lch_to_srgb_hex(&Lch::new(100.0, 0.0, 0.0)),
|
||||
black: map_lch_to_srgb_hex(&Lch::new(0.0, 0.0, 0.0)),
|
||||
light_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, false),
|
||||
dark_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, true),
|
||||
swatches,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,19 +70,31 @@ impl MaterialDesign3Scheme {
|
||||
name: String,
|
||||
color: String,
|
||||
) -> Result<(), errors::ColorError> {
|
||||
let custom_color = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(&color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.clone()))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let custom_color: Lch = Srgb::from_str(&color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.clone()))?
|
||||
.into_format::<f32>()
|
||||
.into_color();
|
||||
let hue = custom_color.hue.into_positive_degrees();
|
||||
let palette = TonalPalette::from_hue_and_chroma(hue, custom_color.chroma);
|
||||
self.light_baseline.add_custom_set(name.clone(), &palette);
|
||||
self.dark_baseline.add_custom_set(name.clone(), &palette);
|
||||
self.swatches.insert(name, M3PaletteSwatch::new(&palette));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn full_custom(
|
||||
light_baseline: M3BaselineColors,
|
||||
dark_baseline: M3BaselineColors,
|
||||
swatches: HashMap<String, M3PaletteSwatch>,
|
||||
) -> Self {
|
||||
Self {
|
||||
white: map_lch_to_srgb_hex(&Lch::new(100.0, 0.0, 0.0)),
|
||||
black: map_lch_to_srgb_hex(&Lch::new(0.0, 0.0, 0.0)),
|
||||
light_baseline,
|
||||
dark_baseline,
|
||||
swatches,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeExport for MaterialDesign3Scheme {
|
||||
@@ -84,10 +105,44 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||
css_variables.push(format!("--color-black: #{};", self.black));
|
||||
css_variables.extend(self.light_baseline.to_css_variables());
|
||||
css_variables.extend(self.dark_baseline.to_css_variables());
|
||||
for (name, swatch) in &self.swatches {
|
||||
css_variables.extend(swatch.to_css_variables(name));
|
||||
}
|
||||
|
||||
css_variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_css_auto_scheme_variables(&self) -> String {
|
||||
let mut auto_scheme_variables = Vec::new();
|
||||
let mut keys = LinkedHashSet::new();
|
||||
let light_baseline = self.light_baseline.to_css_auto_scheme_collection();
|
||||
let dark_baseline = self.dark_baseline.to_css_auto_scheme_collection();
|
||||
|
||||
auto_scheme_variables.push(format!("--color-white: #{};", self.white));
|
||||
auto_scheme_variables.push(format!("--color-black: #{};", self.black));
|
||||
keys.extend(light_baseline.keys().cloned());
|
||||
keys.extend(dark_baseline.keys().cloned());
|
||||
for key in keys {
|
||||
match (light_baseline.get(&key), dark_baseline.get(&key)) {
|
||||
(Some(light), Some(dark)) => {
|
||||
auto_scheme_variables.push(format!(
|
||||
"--color-{}: light-dark(#{}, #{});",
|
||||
key, light, dark
|
||||
));
|
||||
}
|
||||
(Some(color), None) | (None, Some(color)) => {
|
||||
auto_scheme_variables.push(format!("--color-{}: #{};", key, color));
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
for (name, swatch) in &self.swatches {
|
||||
auto_scheme_variables.extend(swatch.to_css_variables(name));
|
||||
}
|
||||
|
||||
auto_scheme_variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut scss_variables = Vec::new();
|
||||
|
||||
@@ -95,6 +150,9 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||
scss_variables.push(format!("$color-black: #{};", self.black));
|
||||
scss_variables.extend(self.light_baseline.to_scss_variables());
|
||||
scss_variables.extend(self.dark_baseline.to_scss_variables());
|
||||
for (name, swatch) in &self.swatches {
|
||||
scss_variables.extend(swatch.to_scss_variables(name));
|
||||
}
|
||||
|
||||
scss_variables.join("\n")
|
||||
}
|
||||
@@ -103,15 +161,14 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||
let mut js_object = Vec::new();
|
||||
|
||||
js_object.push("const colorScheme = {".to_string());
|
||||
js_object.push(format!(" white: '#{}'", self.white));
|
||||
js_object.push(format!(" black: '#{}'", self.black));
|
||||
js_object.push(format!(" white: '#{}',", self.white));
|
||||
js_object.push(format!(" black: '#{}',", self.black));
|
||||
js_object.push(" light: {".to_string());
|
||||
js_object.extend(
|
||||
self.light_baseline
|
||||
.to_javascript_object_fields()
|
||||
.into_iter()
|
||||
.map(|s| format!(" {}", s))
|
||||
.collect::<Vec<String>>(),
|
||||
.map(|s| format!(" {}", s)),
|
||||
);
|
||||
js_object.push(" },".to_string());
|
||||
js_object.push(" dark: {".to_string());
|
||||
@@ -119,12 +176,21 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||
self.dark_baseline
|
||||
.to_javascript_object_fields()
|
||||
.into_iter()
|
||||
.map(|s| format!(" {}", s))
|
||||
.collect::<Vec<String>>(),
|
||||
.map(|s| format!(" {}", s)),
|
||||
);
|
||||
js_object.push(" },".to_string());
|
||||
js_object.push(" swatches: {".to_string());
|
||||
for (name, swatch) in &self.swatches {
|
||||
js_object.extend(
|
||||
swatch
|
||||
.to_javascript_object_fields(name)
|
||||
.into_iter()
|
||||
.map(|s| format!(" {}", s)),
|
||||
);
|
||||
}
|
||||
js_object.push(" }".to_string());
|
||||
js_object.push("}".to_string());
|
||||
|
||||
js_object.join(",\n")
|
||||
js_object.join("\n")
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_cam16jch_to_srgb_hex;
|
||||
use crate::convert::map_lch_to_srgb_hex;
|
||||
|
||||
use super::tonal_palette::TonalPalette;
|
||||
|
||||
@@ -9,6 +10,7 @@ pub struct M3SurfaceSet {
|
||||
pub root: String,
|
||||
pub dim: String,
|
||||
pub bright: String,
|
||||
pub variant: String,
|
||||
pub container: String,
|
||||
pub container_lowest: String,
|
||||
pub container_low: String,
|
||||
@@ -25,6 +27,7 @@ impl M3SurfaceSet {
|
||||
let root = neutral.tone(98.0);
|
||||
let dim = neutral.tone(87.0);
|
||||
let bright = neutral.tone(98.0);
|
||||
let variant = neutral_variant.tone(90.0);
|
||||
let container = neutral.tone(94.0);
|
||||
let container_lowest = neutral.tone(100.0);
|
||||
let container_low = neutral.tone(96.0);
|
||||
@@ -36,18 +39,19 @@ impl M3SurfaceSet {
|
||||
let on_inverse = neutral_variant.tone(95.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
dim: map_cam16jch_to_srgb_hex(&dim),
|
||||
bright: map_cam16jch_to_srgb_hex(&bright),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
container_lowest: map_cam16jch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_cam16jch_to_srgb_hex(&container_low),
|
||||
container_high: map_cam16jch_to_srgb_hex(&container_high),
|
||||
container_highest: map_cam16jch_to_srgb_hex(&container_highest),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
on_root_variant: map_cam16jch_to_srgb_hex(&on_root_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
on_inverse: map_cam16jch_to_srgb_hex(&on_inverse),
|
||||
root: map_lch_to_srgb_hex(&root),
|
||||
dim: map_lch_to_srgb_hex(&dim),
|
||||
bright: map_lch_to_srgb_hex(&bright),
|
||||
variant: map_lch_to_srgb_hex(&variant),
|
||||
container: map_lch_to_srgb_hex(&container),
|
||||
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_lch_to_srgb_hex(&container_low),
|
||||
container_high: map_lch_to_srgb_hex(&container_high),
|
||||
container_highest: map_lch_to_srgb_hex(&container_highest),
|
||||
on_root: map_lch_to_srgb_hex(&on_root),
|
||||
on_root_variant: map_lch_to_srgb_hex(&on_root_variant),
|
||||
inverse: map_lch_to_srgb_hex(&inverse),
|
||||
on_inverse: map_lch_to_srgb_hex(&on_inverse),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +59,7 @@ impl M3SurfaceSet {
|
||||
let root = neutral.tone(6.0);
|
||||
let dim = neutral.tone(6.0);
|
||||
let bright = neutral.tone(24.0);
|
||||
let variant = neutral_variant.tone(30.0);
|
||||
let container = neutral.tone(12.0);
|
||||
let container_lowest = neutral.tone(4.0);
|
||||
let container_low = neutral.tone(10.0);
|
||||
@@ -66,107 +71,145 @@ impl M3SurfaceSet {
|
||||
let on_inverse = neutral_variant.tone(20.0);
|
||||
|
||||
Self {
|
||||
root: map_cam16jch_to_srgb_hex(&root),
|
||||
dim: map_cam16jch_to_srgb_hex(&dim),
|
||||
bright: map_cam16jch_to_srgb_hex(&bright),
|
||||
container: map_cam16jch_to_srgb_hex(&container),
|
||||
container_lowest: map_cam16jch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_cam16jch_to_srgb_hex(&container_low),
|
||||
container_high: map_cam16jch_to_srgb_hex(&container_high),
|
||||
container_highest: map_cam16jch_to_srgb_hex(&container_highest),
|
||||
on_root: map_cam16jch_to_srgb_hex(&on_root),
|
||||
on_root_variant: map_cam16jch_to_srgb_hex(&on_root_variant),
|
||||
inverse: map_cam16jch_to_srgb_hex(&inverse),
|
||||
on_inverse: map_cam16jch_to_srgb_hex(&on_inverse),
|
||||
root: map_lch_to_srgb_hex(&root),
|
||||
dim: map_lch_to_srgb_hex(&dim),
|
||||
bright: map_lch_to_srgb_hex(&bright),
|
||||
variant: map_lch_to_srgb_hex(&variant),
|
||||
container: map_lch_to_srgb_hex(&container),
|
||||
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_lch_to_srgb_hex(&container_low),
|
||||
container_high: map_lch_to_srgb_hex(&container_high),
|
||||
container_highest: map_lch_to_srgb_hex(&container_highest),
|
||||
on_root: map_lch_to_srgb_hex(&on_root),
|
||||
on_root_variant: map_lch_to_srgb_hex(&on_root_variant),
|
||||
inverse: map_lch_to_srgb_hex(&inverse),
|
||||
on_inverse: map_lch_to_srgb_hex(&on_inverse),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
|
||||
css_variables.push(format!("--color-{}-surface: ${};", prefix, self.root));
|
||||
css_variables.push(format!("--color-{}-surface-dim: ${};", prefix, self.dim));
|
||||
css_variables.push(format!("--color-{}-surface: #{};", prefix, self.root));
|
||||
css_variables.push(format!("--color-{}-surface-dim: #{};", prefix, self.dim));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-bright: ${};",
|
||||
"--color-{}-surface-bright: #{};",
|
||||
prefix, self.bright
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container: ${};",
|
||||
"--color-{}-surface-variant: #{};",
|
||||
prefix, self.variant
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container: #{};",
|
||||
prefix, self.container
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-lowest: ${};",
|
||||
"--color-{}-surface-container-lowest: #{};",
|
||||
prefix, self.container_lowest
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-low: ${};",
|
||||
"--color-{}-surface-container-low: #{};",
|
||||
prefix, self.container_low
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-high: ${};",
|
||||
"--color-{}-surface-container-high: #{};",
|
||||
prefix, self.container_high
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container-highest: ${};",
|
||||
"--color-{}-surface-container-highest: #{};",
|
||||
prefix, self.container_highest
|
||||
));
|
||||
css_variables.push(format!("--color-{}-on-surface: ${};", prefix, self.on_root));
|
||||
css_variables.push(format!("--color-{}-on-surface: #{};", prefix, self.on_root));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-on-surface-variant: ${};",
|
||||
"--color-{}-on-surface-variant: #{};",
|
||||
prefix, self.on_root_variant
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-inverse-surface: ${};",
|
||||
"--color-{}-inverse-surface: #{};",
|
||||
prefix, self.inverse
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-inverse-on-surface: ${};",
|
||||
"--color-{}-inverse-on-surface: #{};",
|
||||
prefix, self.on_inverse
|
||||
));
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self) -> LinkedHashMap<String, String> {
|
||||
let mut auto_scheme_collection = LinkedHashMap::new();
|
||||
|
||||
auto_scheme_collection.insert(format!("surface"), self.root.clone());
|
||||
auto_scheme_collection.insert(format!("surface-dim"), self.dim.clone());
|
||||
auto_scheme_collection.insert(format!("surface-bright"), self.bright.clone());
|
||||
auto_scheme_collection.insert(format!("surface-variant"), self.variant.clone());
|
||||
auto_scheme_collection.insert(format!("surface-container"), self.container.clone());
|
||||
auto_scheme_collection.insert(
|
||||
format!("surface-container-lowest"),
|
||||
self.container_lowest.clone(),
|
||||
);
|
||||
auto_scheme_collection.insert(format!("surface-container-low"), self.container_low.clone());
|
||||
auto_scheme_collection.insert(
|
||||
format!("surface-container-high"),
|
||||
self.container_high.clone(),
|
||||
);
|
||||
auto_scheme_collection.insert(
|
||||
format!("surface-container-highest"),
|
||||
self.container_highest.clone(),
|
||||
);
|
||||
auto_scheme_collection.insert(format!("on-surface"), self.on_root.clone());
|
||||
auto_scheme_collection.insert(format!("on-surface-variant"), self.on_root_variant.clone());
|
||||
auto_scheme_collection.insert(format!("inverse-surface"), self.inverse.clone());
|
||||
auto_scheme_collection.insert(format!("inverse-on-surface"), self.on_inverse.clone());
|
||||
|
||||
auto_scheme_collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut scss_variables = Vec::new();
|
||||
|
||||
scss_variables.push(format!("$color-{}-surface: ${};", prefix, self.root));
|
||||
scss_variables.push(format!("$color-{}-surface-dim: ${};", prefix, self.dim));
|
||||
scss_variables.push(format!("$color-{}-surface: #{};", prefix, self.root));
|
||||
scss_variables.push(format!("$color-{}-surface-dim: #{};", prefix, self.dim));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-bright: ${};",
|
||||
"$color-{}-surface-bright: #{};",
|
||||
prefix, self.bright
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container: ${};",
|
||||
"$color-{}-surface-variant: #{};",
|
||||
prefix, self.variant
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container: #{};",
|
||||
prefix, self.container
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-lowest: ${};",
|
||||
"$color-{}-surface-container-lowest: #{};",
|
||||
prefix, self.container_lowest
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-low: ${};",
|
||||
"$color-{}-surface-container-low: #{};",
|
||||
prefix, self.container_low
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-high: ${};",
|
||||
"$color-{}-surface-container-high: #{};",
|
||||
prefix, self.container_high
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container-highest: ${};",
|
||||
"$color-{}-surface-container-highest: #{};",
|
||||
prefix, self.container_highest
|
||||
));
|
||||
scss_variables.push(format!("$color-{}-on-surface: ${};", prefix, self.on_root));
|
||||
scss_variables.push(format!("$color-{}-on-surface: #{};", prefix, self.on_root));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-on-surface-variant: ${};",
|
||||
"$color-{}-on-surface-variant: #{};",
|
||||
prefix, self.on_root_variant
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-inverse-surface: ${};",
|
||||
"$color-{}-inverse-surface: #{};",
|
||||
prefix, self.inverse
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-inverse-on-surface: ${};",
|
||||
"$color-{}-inverse-on-surface: #{};",
|
||||
prefix, self.on_inverse
|
||||
));
|
||||
|
||||
@@ -179,6 +222,7 @@ impl M3SurfaceSet {
|
||||
js_object_fields.push(format!("{}Surface: '#{}',", prefix, self.root));
|
||||
js_object_fields.push(format!("{}SurfaceDim: '#{}',", prefix, self.dim));
|
||||
js_object_fields.push(format!("{}SurfaceBright: '#{}',", prefix, self.bright));
|
||||
js_object_fields.push(format!("{}SurfaceVariant: '#{}',", prefix, self.variant));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainer: '#{}',",
|
||||
prefix, self.container
|
||||
|
74
color-module/src/schemes/material_design_3/swatch.rs
Normal file
74
color-module/src/schemes/material_design_3/swatch.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::convert::map_lch_to_srgb_hex;
|
||||
|
||||
use super::TonalPalette;
|
||||
|
||||
static SWATCH_TONES: [u8; 18] = [
|
||||
0, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100,
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct M3PaletteSwatch(HashMap<u8, String>);
|
||||
|
||||
impl M3PaletteSwatch {
|
||||
pub fn new(palette: &TonalPalette) -> Self {
|
||||
let mut swatch = HashMap::new();
|
||||
for &tone in SWATCH_TONES.iter() {
|
||||
let color = palette.tone(tone as f32);
|
||||
swatch.insert(tone, map_lch_to_srgb_hex(&color));
|
||||
}
|
||||
Self(swatch)
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
let name = name.replace('_', "-").to_lowercase();
|
||||
|
||||
for &tone in SWATCH_TONES.iter() {
|
||||
let color = self.0.get(&tone).unwrap();
|
||||
variable_lines.push(format!("--color-swatch-{}-{}: #{};", name, tone, color));
|
||||
}
|
||||
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, name: &str) -> Vec<String> {
|
||||
let mut variable_lines = Vec::new();
|
||||
let name = name.replace('_', "-").to_lowercase();
|
||||
|
||||
for &tone in SWATCH_TONES.iter() {
|
||||
let color = self.0.get(&tone).unwrap();
|
||||
variable_lines.push(format!("$color-swatch-{}-{}: #{};", name, tone, color));
|
||||
}
|
||||
|
||||
variable_lines
|
||||
}
|
||||
|
||||
pub fn to_javascript_object_fields(&self, name: &str) -> Vec<String> {
|
||||
let mut js_object = Vec::new();
|
||||
let name = name
|
||||
.split('_')
|
||||
.enumerate()
|
||||
.map(|(i, part)| {
|
||||
if i == 0 {
|
||||
part.to_string()
|
||||
} else {
|
||||
let mut c = part.chars();
|
||||
c.next().unwrap().to_uppercase().collect::<String>() + c.as_str()
|
||||
}
|
||||
})
|
||||
.collect::<String>();
|
||||
|
||||
js_object.push(format!("{}: {{", name));
|
||||
for &tone in SWATCH_TONES.iter() {
|
||||
let color = self.0.get(&tone).unwrap();
|
||||
js_object.push(format!(" {}: '#{}',", tone, color));
|
||||
}
|
||||
js_object.push("},".to_string());
|
||||
|
||||
js_object
|
||||
}
|
||||
}
|
@@ -1,15 +1,12 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use palette::{
|
||||
cam16::{Cam16Jch, Parameters},
|
||||
IntoColor, Srgb,
|
||||
};
|
||||
use palette::{cam16::Cam16Jch, IntoColor, Lch, Srgb};
|
||||
|
||||
use crate::errors;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TonalPalette {
|
||||
pub key_color: Cam16Jch<f32>,
|
||||
pub key_color: Lch,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -30,7 +27,7 @@ fn find_max_chroma(cache: &mut Vec<(f32, f32)>, hue: f32, tone: f32) -> f32 {
|
||||
chroma
|
||||
}
|
||||
|
||||
fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
|
||||
fn from_hue_and_chroma(hue: f32, chroma: f32) -> Lch {
|
||||
let mut max_chroma_cache = Vec::new();
|
||||
let hue = if hue >= 360.0 { hue - 360.0 } else { hue };
|
||||
const PIVOT_TONE: f32 = 50.0;
|
||||
@@ -51,7 +48,7 @@ fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
|
||||
upper_tone = mid_tone;
|
||||
} else {
|
||||
if approximately_equal(lower_tone, mid_tone) {
|
||||
return Cam16Jch::new(lower_tone, chroma, hue);
|
||||
return Lch::new(lower_tone, chroma, hue);
|
||||
}
|
||||
lower_tone = mid_tone;
|
||||
}
|
||||
@@ -63,20 +60,17 @@ fn from_hue_and_chroma(hue: f32, chroma: f32) -> Cam16Jch<f32> {
|
||||
}
|
||||
}
|
||||
}
|
||||
Cam16Jch::new(lower_tone, chroma, hue)
|
||||
Lch::new(lower_tone, chroma, hue)
|
||||
}
|
||||
|
||||
impl TryFrom<String> for TonalPalette {
|
||||
type Error = errors::ColorError;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
let key_color = Cam16Jch::from_xyz(
|
||||
Srgb::from_str(&value)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(value))?
|
||||
.into_format::<f32>()
|
||||
.into_color(),
|
||||
Parameters::default_static_wp(40.0),
|
||||
);
|
||||
let key_color: Lch = Srgb::from_str(&value)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(value))?
|
||||
.into_format::<f32>()
|
||||
.into_color();
|
||||
Ok(TonalPalette { key_color })
|
||||
}
|
||||
}
|
||||
@@ -87,8 +81,8 @@ impl TonalPalette {
|
||||
TonalPalette { key_color }
|
||||
}
|
||||
|
||||
pub fn tone(&self, tone: f32) -> Cam16Jch<f32> {
|
||||
let toned_color = Cam16Jch::new(tone, self.key_color.chroma, self.key_color.hue);
|
||||
pub fn tone(&self, tone: f32) -> Lch {
|
||||
let toned_color = Lch::new(tone, self.key_color.chroma, self.key_color.hue);
|
||||
|
||||
toned_color
|
||||
}
|
||||
|
379
color-module/src/schemes/material_design_3_dynamic/constants.rs
Normal file
379
color-module/src/schemes/material_design_3_dynamic/constants.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
use enum_iterator::Sequence;
|
||||
use palette::{
|
||||
color_theory::{Analogous, Complementary},
|
||||
Lch,
|
||||
};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use strum::Display;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
use crate::{
|
||||
schemes::material_design_3::TonalPalette,
|
||||
theory::{harmonize_hue, sanitize_hue_degrees},
|
||||
};
|
||||
|
||||
use super::dynamic_color::CustomPaletteGenerator;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Display, Sequence, Serialize_repr, Deserialize_repr)]
|
||||
#[wasm_bindgen]
|
||||
#[repr(u8)]
|
||||
pub enum Variant {
|
||||
Monochrome,
|
||||
Neutral,
|
||||
TonalSpot,
|
||||
Vibrant,
|
||||
Expressive,
|
||||
Fidelity,
|
||||
Content,
|
||||
Rainbow,
|
||||
FruitSalad,
|
||||
}
|
||||
|
||||
impl Variant {
|
||||
pub fn label(&self) -> String {
|
||||
match self {
|
||||
Variant::Monochrome => "Monochrome".to_string(),
|
||||
Variant::Neutral => "Neutral".to_string(),
|
||||
Variant::TonalSpot => "Tonal Spot".to_string(),
|
||||
Variant::Vibrant => "Vibrant".to_string(),
|
||||
Variant::Expressive => "Expressive".to_string(),
|
||||
Variant::Fidelity => "Fidelity".to_string(),
|
||||
Variant::Content => "Content".to_string(),
|
||||
Variant::Rainbow => "Rainbow".to_string(),
|
||||
Variant::FruitSalad => "Fruit Salad".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_u8(value: u8) -> Variant {
|
||||
match value {
|
||||
0 => Variant::Monochrome,
|
||||
1 => Variant::Neutral,
|
||||
2 => Variant::TonalSpot,
|
||||
3 => Variant::Vibrant,
|
||||
4 => Variant::Expressive,
|
||||
5 => Variant::Fidelity,
|
||||
6 => Variant::Content,
|
||||
7 => Variant::Rainbow,
|
||||
8 => Variant::FruitSalad,
|
||||
_ => Variant::Expressive,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hues(&self) -> Vec<f32> {
|
||||
match self {
|
||||
Variant::Vibrant => vec![0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0],
|
||||
Variant::Expressive => vec![0.0, 21.0, 51.0, 121.0, 151.0, 191.0, 271.0, 321.0, 360.0],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secondary_rotation(&self) -> Vec<f32> {
|
||||
match self {
|
||||
Variant::Vibrant => vec![18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0],
|
||||
Variant::Expressive => vec![45.0, 95.0, 45.0, 20.0, 45.0, 90.0, 45.0, 45.0, 45.0],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tertiary_rotation(&self) -> Vec<f32> {
|
||||
match self {
|
||||
Variant::Vibrant => vec![35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0],
|
||||
Variant::Expressive => vec![120.0, 120.0, 20.0, 45.0, 20.0, 15.0, 20.0, 120.0, 120.0],
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_palette(
|
||||
&self,
|
||||
source_color: Lch,
|
||||
harmonize_customs: bool,
|
||||
) -> (
|
||||
TonalPalette,
|
||||
TonalPalette,
|
||||
TonalPalette,
|
||||
TonalPalette,
|
||||
TonalPalette,
|
||||
CustomPaletteGenerator,
|
||||
) {
|
||||
match self {
|
||||
Variant::Monochrome => (
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
0.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 0.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::Neutral => (
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 12.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 8.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 2.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 2.0),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
12.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 12.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::TonalSpot => (
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 36.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 60.0),
|
||||
24.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 6.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 8.0),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
36.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 36.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::Vibrant => (
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 200.0),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
rotate_hue(&source_color, &self.hues(), &self.secondary_rotation()),
|
||||
24.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
rotate_hue(&source_color, &self.hues(), &self.tertiary_rotation()),
|
||||
32.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 10.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 12.0),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
200.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 200.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::Expressive => (
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 240.0),
|
||||
40.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
rotate_hue(&source_color, &self.hues(), &self.secondary_rotation()),
|
||||
24.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
rotate_hue(&source_color, &self.hues(), &self.tertiary_rotation()),
|
||||
32.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees() + 15.0,
|
||||
8.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees() + 15.0,
|
||||
12.0,
|
||||
),
|
||||
if harmonize_customs {
|
||||
let source_hue =
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 240.0);
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
40.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 40.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::Fidelity => (
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
source_color.chroma,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
(source_color.chroma - 32.0).max(source_color.chroma * 0.5),
|
||||
),
|
||||
TonalPalette {
|
||||
key_color: fix_disliked(&source_color.complementary()),
|
||||
},
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
source_color.chroma / 8.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
source_color.chroma / 8.0 + 4.0,
|
||||
),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
let source_chroma = source_color.chroma;
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
source_chroma,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
let source_chroma = source_color.chroma;
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
c.hue.into_positive_degrees(),
|
||||
source_chroma,
|
||||
)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::Content => (
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
source_color.chroma,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
(source_color.chroma - 32.0).max(source_color.chroma * 0.5),
|
||||
),
|
||||
TonalPalette {
|
||||
key_color: fix_disliked(&source_color.analogous().1),
|
||||
},
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
source_color.chroma / 8.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
source_color.hue.into_positive_degrees(),
|
||||
source_color.chroma / 8.0 + 4.0,
|
||||
),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
c.chroma,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), c.chroma)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::Rainbow => (
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 48.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 60.0),
|
||||
24.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
|
||||
if harmonize_customs {
|
||||
let source_hue = source_color.hue.into_positive_degrees();
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
48.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 48.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
Variant::FruitSalad => (
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() - 50.0),
|
||||
48.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() - 50.0),
|
||||
36.0,
|
||||
),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 36.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 10.0),
|
||||
TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
|
||||
if harmonize_customs {
|
||||
let source_hue =
|
||||
sanitize_hue_degrees(source_color.hue.into_positive_degrees() - 50.0);
|
||||
Box::new(move |c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
harmonize_hue(c.hue.into_positive_degrees(), source_hue),
|
||||
48.0,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Box::new(|c: &Lch| {
|
||||
TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 48.0)
|
||||
})
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate_hue(source: &Lch, hues: &Vec<f32>, rotations: &Vec<f32>) -> f32 {
|
||||
let source_hue = source.hue.into_positive_degrees();
|
||||
if rotations.len() == 1 {
|
||||
return sanitize_hue_degrees(source_hue + rotations[0]);
|
||||
}
|
||||
let hues_size = hues.len();
|
||||
for i in 0..=hues_size - 2 {
|
||||
let hue = hues[i];
|
||||
let next_hue = hues[i + 1];
|
||||
if hue < source_hue && source_hue < next_hue {
|
||||
return sanitize_hue_degrees(source_hue + rotations[i]);
|
||||
}
|
||||
}
|
||||
|
||||
source_hue
|
||||
}
|
||||
|
||||
pub fn fix_disliked(color: &Lch) -> Lch {
|
||||
let hue = color.hue.into_positive_degrees().round() >= 90.0
|
||||
&& color.hue.into_positive_degrees().round() <= 111.0;
|
||||
let chroma = color.chroma.round() > 16.0;
|
||||
let lightness = color.l.round() < 65.0;
|
||||
|
||||
if hue && chroma && lightness {
|
||||
Lch::new(70.0, color.chroma, color.hue)
|
||||
} else {
|
||||
color.clone()
|
||||
}
|
||||
}
|
102
color-module/src/schemes/material_design_3_dynamic/contrast.rs
Normal file
102
color-module/src/schemes/material_design_3_dynamic/contrast.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
fn lab_inv_f(ft: f32) -> f32 {
|
||||
let e = 216.0 / 24389.0;
|
||||
let k = 24389.0 / 27.0;
|
||||
let ft3 = ft * ft * ft;
|
||||
if ft3 > e {
|
||||
ft3
|
||||
} else {
|
||||
(116.0 * ft - 16.0) / k
|
||||
}
|
||||
}
|
||||
|
||||
fn lab_f(t: f32) -> f32 {
|
||||
let e = 216.0 / 24389.0;
|
||||
let k = 24389.0 / 27.0;
|
||||
if t > e {
|
||||
t.powf(1.0 / 3.0)
|
||||
} else {
|
||||
(k * t + 16.0) / 116.0
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn y_from_lstar(lstar: f32) -> f32 {
|
||||
100.0 * lab_inv_f((lstar + 16.0) / 116.0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn lstar_from_y(y: f32) -> f32 {
|
||||
lab_f(y / 100.0) * 116.0 - 16.0
|
||||
}
|
||||
|
||||
pub fn ratio_of_ys(y1: f32, y2: f32) -> f32 {
|
||||
let lighter = y1.max(y2);
|
||||
let darker = y1.min(y2);
|
||||
(lighter + 5.0) / (darker + 5.0)
|
||||
}
|
||||
|
||||
pub fn ratio_of_tones(a: f32, b: f32) -> f32 {
|
||||
let tone_a = a.clamp(0.0, 100.0);
|
||||
let tone_b = b.clamp(0.0, 100.0);
|
||||
ratio_of_ys(y_from_lstar(tone_a), y_from_lstar(tone_b))
|
||||
}
|
||||
|
||||
pub fn lighter(tone: f32, ratio: f32) -> f32 {
|
||||
if tone < 0.0 || tone > 100.0 {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
let dark_y = y_from_lstar(tone);
|
||||
let light_y = ratio * (dark_y + 5.0) - 5.0;
|
||||
let real_contrast = ratio_of_ys(light_y, dark_y);
|
||||
let delta = (real_contrast - ratio).abs();
|
||||
if real_contrast < ratio && delta > 0.04 {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
let return_value = lstar_from_y(light_y) + 0.4;
|
||||
if return_value < 0.0 || return_value > 100.0 {
|
||||
return -1.0;
|
||||
}
|
||||
return_value
|
||||
}
|
||||
|
||||
pub fn darker(tone: f32, ratio: f32) -> f32 {
|
||||
if tone < 0.0 || tone > 100.0 {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
let light_y = y_from_lstar(tone);
|
||||
let dark_y = ((light_y + 5.0) / ratio) - 5.0;
|
||||
let real_contrast = ratio_of_ys(light_y, dark_y);
|
||||
|
||||
let delta = (real_contrast - ratio).abs();
|
||||
|
||||
if real_contrast < ratio && delta > 0.04 {
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
let return_value = lstar_from_y(dark_y) - 0.4;
|
||||
if return_value < 0.0 || return_value > 100.0 {
|
||||
return -1.0;
|
||||
}
|
||||
return_value
|
||||
}
|
||||
|
||||
pub fn unsafe_lighter(tone: f32, ratio: f32) -> f32 {
|
||||
let safe_lighter = lighter(tone, ratio);
|
||||
if safe_lighter < 0.0 {
|
||||
100.0
|
||||
} else {
|
||||
safe_lighter
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsafe_darker(tone: f32, ratio: f32) -> f32 {
|
||||
let safe_darker = darker(tone, ratio);
|
||||
if safe_darker < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
safe_darker
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ContrastCurve {
|
||||
low: f32,
|
||||
normal: f32,
|
||||
medium: f32,
|
||||
high: f32,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn lerp(start: f32, stop: f32, amount: f32) -> f32 {
|
||||
(1.0 - start) * start + amount * stop
|
||||
}
|
||||
|
||||
impl ContrastCurve {
|
||||
pub fn new(low: f32, normal: f32, medium: f32, high: f32) -> Self {
|
||||
Self {
|
||||
low,
|
||||
normal,
|
||||
medium,
|
||||
high,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, contrast_level: f32) -> f32 {
|
||||
if contrast_level <= -1.0 {
|
||||
self.low
|
||||
} else if contrast_level < 0.0 {
|
||||
lerp(self.low, self.normal, (contrast_level + 1.0) / 1.0)
|
||||
} else if contrast_level < 0.5 {
|
||||
lerp(self.normal, self.medium, contrast_level / 0.5)
|
||||
} else if contrast_level < 1.0 {
|
||||
lerp(self.medium, self.high, (contrast_level - 0.5) / 0.5)
|
||||
} else {
|
||||
self.high
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,245 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use palette::Lch;
|
||||
|
||||
use crate::schemes::material_design_3::TonalPalette;
|
||||
|
||||
use super::{contrast_curve::ContrastCurve, dynamic_scheme::DynamicScheme};
|
||||
|
||||
pub type TonalPaletteGenerator = Box<dyn Fn(&DynamicScheme) -> TonalPalette>;
|
||||
pub type ToneSearcher = Box<dyn Fn(&DynamicScheme) -> f32>;
|
||||
pub type DynamicColorSearcher = Box<dyn Fn(&DynamicScheme) -> Rc<DynamicColor>>;
|
||||
pub type ToneDeltaPairGenerator = Box<dyn Fn(&DynamicScheme) -> ToneDeltaPair>;
|
||||
pub type CustomPaletteGenerator = Box<dyn Fn(&Lch) -> TonalPalette>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum TonePolarity {
|
||||
Darker,
|
||||
Lighter,
|
||||
Nearer,
|
||||
Farther,
|
||||
}
|
||||
|
||||
pub struct DynamicColor {
|
||||
name: String,
|
||||
palette: TonalPaletteGenerator,
|
||||
tone: ToneSearcher,
|
||||
is_background: Option<bool>,
|
||||
background: Option<DynamicColorSearcher>,
|
||||
secondary_background: Option<DynamicColorSearcher>,
|
||||
contrast_curve: Option<ContrastCurve>,
|
||||
tone_delta_pairs: Option<ToneDeltaPairGenerator>,
|
||||
}
|
||||
|
||||
pub struct ToneDeltaPair {
|
||||
pub role_a: Rc<DynamicColor>,
|
||||
pub role_b: Rc<DynamicColor>,
|
||||
pub delta: f32,
|
||||
pub polarity: TonePolarity,
|
||||
pub togather: bool,
|
||||
}
|
||||
|
||||
impl DynamicColor {
|
||||
pub fn new(
|
||||
name: Option<&str>,
|
||||
palette: TonalPaletteGenerator,
|
||||
tone: ToneSearcher,
|
||||
is_background: Option<bool>,
|
||||
background: Option<DynamicColorSearcher>,
|
||||
secondary_background: Option<DynamicColorSearcher>,
|
||||
contrast_curve: Option<ContrastCurve>,
|
||||
tone_delta_pairs: Option<ToneDeltaPairGenerator>,
|
||||
) -> Self {
|
||||
DynamicColor {
|
||||
name: name.unwrap_or("").to_string(),
|
||||
palette,
|
||||
tone,
|
||||
is_background: is_background.or(Some(false)),
|
||||
background,
|
||||
secondary_background,
|
||||
contrast_curve,
|
||||
tone_delta_pairs,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tone(&self, scheme: &DynamicScheme) -> f32 {
|
||||
(self.tone)(scheme)
|
||||
}
|
||||
|
||||
pub fn get_lch(&self, scheme: &DynamicScheme) -> Lch {
|
||||
let tone = self.get_tone(scheme);
|
||||
(self.palette)(scheme).tone(tone)
|
||||
}
|
||||
|
||||
pub fn get_tone(&self, scheme: &DynamicScheme) -> f32 {
|
||||
let decreasing_contrast = scheme.contrast_level < 0.0;
|
||||
|
||||
if let Some(pair_generator) = &self.tone_delta_pairs {
|
||||
let tone_delta = pair_generator(scheme);
|
||||
let bg = (self.background.as_ref().unwrap())(scheme);
|
||||
let bg_tone = bg.get_tone(scheme);
|
||||
let is_nearer = tone_delta.polarity == TonePolarity::Nearer
|
||||
|| (tone_delta.polarity == TonePolarity::Lighter && !scheme.is_dark)
|
||||
|| (tone_delta.polarity == TonePolarity::Darker && scheme.is_dark);
|
||||
let (nearer, farther) = if is_nearer {
|
||||
(&tone_delta.role_a, &tone_delta.role_b)
|
||||
} else {
|
||||
(&tone_delta.role_b, &tone_delta.role_a)
|
||||
};
|
||||
let expansion_factor = if scheme.is_dark { 1.0 } else { -1.0 };
|
||||
|
||||
let n_contrast = (nearer.contrast_curve.as_ref().unwrap()).get(scheme.contrast_level);
|
||||
let f_contrast = (farther.contrast_curve.as_ref().unwrap()).get(scheme.contrast_level);
|
||||
|
||||
let n_initial_tone = nearer.tone(scheme);
|
||||
let mut n_tone =
|
||||
if super::contrast::ratio_of_tones(bg_tone, n_initial_tone) >= n_contrast {
|
||||
n_initial_tone
|
||||
} else {
|
||||
foreground_tone(bg_tone, n_contrast)
|
||||
};
|
||||
let f_initial_tone = farther.tone(scheme);
|
||||
let mut f_tone =
|
||||
if super::contrast::ratio_of_tones(bg_tone, f_initial_tone) >= f_contrast {
|
||||
f_initial_tone
|
||||
} else {
|
||||
foreground_tone(bg_tone, f_contrast)
|
||||
};
|
||||
|
||||
if decreasing_contrast {
|
||||
n_tone = foreground_tone(bg_tone, n_contrast);
|
||||
f_tone = foreground_tone(bg_tone, f_contrast);
|
||||
}
|
||||
|
||||
if (f_tone - n_tone) * expansion_factor < tone_delta.delta {
|
||||
f_tone = (n_tone + tone_delta.delta * expansion_factor).clamp(0.0, 100.0);
|
||||
}
|
||||
|
||||
if n_tone >= 50.0 && n_tone < 60.0 {
|
||||
if expansion_factor > 0.0 {
|
||||
n_tone = 60.0;
|
||||
f_tone = (n_tone + tone_delta.delta * expansion_factor).max(f_tone);
|
||||
} else {
|
||||
n_tone = 49.0;
|
||||
f_tone = (n_tone + tone_delta.delta * expansion_factor).min(f_tone);
|
||||
}
|
||||
} else if f_tone >= 50.0 && f_tone < 60.0 {
|
||||
if tone_delta.togather {
|
||||
if expansion_factor > 0.0 {
|
||||
n_tone = 60.0;
|
||||
f_tone = (n_tone + tone_delta.delta * expansion_factor).max(f_tone);
|
||||
} else {
|
||||
n_tone = 49.0;
|
||||
f_tone = (n_tone + tone_delta.delta * expansion_factor).min(f_tone);
|
||||
}
|
||||
} else {
|
||||
if expansion_factor > 0.0 {
|
||||
f_tone = 60.0;
|
||||
} else {
|
||||
f_tone = 49.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.name.eq_ignore_ascii_case(&nearer.name) {
|
||||
n_tone
|
||||
} else {
|
||||
f_tone
|
||||
}
|
||||
} else {
|
||||
let mut result = (self.tone)(scheme);
|
||||
|
||||
if self.background.is_none() {
|
||||
return result;
|
||||
}
|
||||
|
||||
let bg_tone = (self.background.as_ref().unwrap())(scheme).get_tone(scheme);
|
||||
let desired_ratio = (self.contrast_curve.as_ref().unwrap()).get(scheme.contrast_level);
|
||||
|
||||
if super::contrast::ratio_of_tones(bg_tone, result) < desired_ratio {
|
||||
result = foreground_tone(bg_tone, desired_ratio);
|
||||
}
|
||||
if decreasing_contrast {
|
||||
result = foreground_tone(bg_tone, desired_ratio);
|
||||
}
|
||||
|
||||
if self.is_background.unwrap_or(false) && result >= 50.0 && result < 60.0 {
|
||||
if super::contrast::ratio_of_tones(49.0, bg_tone) >= desired_ratio {
|
||||
result = 49.0;
|
||||
} else {
|
||||
result = 60.0;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(secondary_background) = &self.secondary_background {
|
||||
let (bg_tone_1, bg_tone_2) = (
|
||||
(self.background.as_ref().unwrap())(scheme).get_tone(scheme),
|
||||
secondary_background(scheme).get_tone(scheme),
|
||||
);
|
||||
let (upper, lower) = (bg_tone_1.max(bg_tone_2), bg_tone_1.min(bg_tone_2));
|
||||
|
||||
if super::contrast::ratio_of_tones(upper, result) >= desired_ratio
|
||||
&& super::contrast::ratio_of_tones(lower, result) >= desired_ratio
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
let light_option = super::contrast::lighter(upper, desired_ratio);
|
||||
let dark_option = super::contrast::darker(lower, desired_ratio);
|
||||
|
||||
let mut availables = vec![];
|
||||
if light_option != -1.0 {
|
||||
availables.push(light_option);
|
||||
}
|
||||
if dark_option != -1.0 {
|
||||
availables.push(dark_option);
|
||||
}
|
||||
|
||||
if prefer_light_foreground(bg_tone_1) || prefer_light_foreground(bg_tone_2) {
|
||||
return if light_option < 0.0 {
|
||||
100.0
|
||||
} else {
|
||||
light_option
|
||||
};
|
||||
}
|
||||
if availables.len() == 1 {
|
||||
return availables[0];
|
||||
}
|
||||
|
||||
return if dark_option < 0.0 { 0.0 } else { dark_option };
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn prefer_light_foreground(tone: f32) -> bool {
|
||||
tone.round() < 60.0
|
||||
}
|
||||
|
||||
pub fn foreground_tone(background_tone: f32, ratio: f32) -> f32 {
|
||||
let lighter_tone = super::contrast::unsafe_lighter(background_tone, ratio);
|
||||
let darker_tone = super::contrast::unsafe_darker(background_tone, ratio);
|
||||
let lighter_ratio = super::contrast::ratio_of_tones(lighter_tone, background_tone);
|
||||
let darker_ratio = super::contrast::ratio_of_tones(darker_tone, background_tone);
|
||||
|
||||
if prefer_light_foreground(background_tone) {
|
||||
let difference = (lighter_ratio - darker_ratio).abs() < 0.1
|
||||
&& lighter_ratio < ratio
|
||||
&& darker_ratio < ratio;
|
||||
if lighter_ratio >= ratio || lighter_ratio >= darker_ratio || difference {
|
||||
lighter_tone
|
||||
} else {
|
||||
darker_tone
|
||||
}
|
||||
} else {
|
||||
if darker_ratio >= ratio || darker_ratio >= lighter_ratio {
|
||||
darker_tone
|
||||
} else {
|
||||
lighter_tone
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use palette::Lch;
|
||||
|
||||
use crate::schemes::material_design_3::TonalPalette;
|
||||
|
||||
use super::constants::Variant;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct DynamicScheme {
|
||||
pub source_color: Lch,
|
||||
pub error_palette: TonalPalette,
|
||||
pub contrast_level: f32,
|
||||
pub variant: Variant,
|
||||
pub is_dark: bool,
|
||||
pub harmonize_customs: bool,
|
||||
pub primary_palette: TonalPalette,
|
||||
pub secondary_palette: TonalPalette,
|
||||
pub tertiary_palette: TonalPalette,
|
||||
pub neutral_palette: TonalPalette,
|
||||
pub neutral_variant_palette: TonalPalette,
|
||||
pub custom_palettes: HashMap<String, TonalPalette>,
|
||||
}
|
||||
|
||||
impl DynamicScheme {
|
||||
pub fn new(
|
||||
source_color: Lch,
|
||||
error_color: Option<Lch>,
|
||||
custom_colors: HashMap<String, Lch>,
|
||||
variant: Variant,
|
||||
contrast_level: f32,
|
||||
is_dark: bool,
|
||||
harmonize_customs: bool,
|
||||
) -> Self {
|
||||
let (
|
||||
primary_palette,
|
||||
secondary_palette,
|
||||
tertiary_palette,
|
||||
neutral_palette,
|
||||
neutral_variant_palette,
|
||||
custom_generator,
|
||||
) = variant.build_palette(source_color, harmonize_customs);
|
||||
|
||||
let custom_palettes = custom_colors
|
||||
.into_iter()
|
||||
.map(|(name, color)| (name, custom_generator(&color)))
|
||||
.collect();
|
||||
|
||||
DynamicScheme {
|
||||
source_color,
|
||||
error_palette: error_color
|
||||
.map(|error_color| {
|
||||
TonalPalette::from_hue_and_chroma(
|
||||
error_color.hue.into_positive_degrees(),
|
||||
error_color.chroma,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| TonalPalette::from_hue_and_chroma(25.0, 48.0)),
|
||||
contrast_level,
|
||||
variant,
|
||||
is_dark,
|
||||
harmonize_customs,
|
||||
primary_palette,
|
||||
secondary_palette,
|
||||
tertiary_palette,
|
||||
neutral_palette,
|
||||
neutral_variant_palette,
|
||||
custom_palettes,
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,917 @@
|
||||
use std::{cell::LazyCell, rc::Rc};
|
||||
|
||||
use crate::cond;
|
||||
|
||||
use super::{
|
||||
constants::{fix_disliked, Variant},
|
||||
contrast_curve::ContrastCurve,
|
||||
dynamic_color::{foreground_tone, DynamicColor, ToneDeltaPair, TonePolarity},
|
||||
dynamic_scheme::DynamicScheme,
|
||||
};
|
||||
|
||||
macro_rules! dynamic_gen {
|
||||
($variable: ident, $name: ident, $palette: expr, $tone: expr) => {
|
||||
pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
))
|
||||
});
|
||||
};
|
||||
($variable: ident, $name: ident, $palette: expr, $tone: expr, $is_background: expr) => {
|
||||
pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
Some($is_background),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
))
|
||||
});
|
||||
};
|
||||
($variable: ident, $name: ident, $palette: expr, $tone: expr, $background: expr, $contrast_curve: expr) => {
|
||||
pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
None,
|
||||
Some(Box::new($background)),
|
||||
None,
|
||||
Some($contrast_curve),
|
||||
None,
|
||||
))
|
||||
});
|
||||
};
|
||||
($variable: ident, $name: ident, $palette: expr, $tone: expr, $is_background: expr, $background: expr, $secondary_background: expr, $contrast_curve: expr, $tone_delta_pairs: expr) => {
|
||||
pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
$is_background,
|
||||
$background,
|
||||
$secondary_background,
|
||||
$contrast_curve,
|
||||
$tone_delta_pairs,
|
||||
))
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_fidelity(scheme: &DynamicScheme) -> bool {
|
||||
scheme.variant == Variant::Fidelity || scheme.variant == Variant::Content
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_monochrome(scheme: &DynamicScheme) -> bool {
|
||||
scheme.variant == Variant::Monochrome
|
||||
}
|
||||
|
||||
fn highest_surface(s: &DynamicScheme) -> Rc<DynamicColor> {
|
||||
cond!(s.is_dark, Rc::clone(&SURFACE_BRIGHT), Rc::clone(&SURFACE))
|
||||
}
|
||||
|
||||
dynamic_gen!(
|
||||
SURFACE,
|
||||
surface,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 6.0, 98.0),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_DIM,
|
||||
surface_dim,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
6.0,
|
||||
ContrastCurve::new(87.0, 87.0, 80.0, 75.0).get(s.contrast_level)
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_BRIGHT,
|
||||
surface_bright,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
ContrastCurve::new(24.0, 24.0, 29.0, 34.0).get(s.contrast_level),
|
||||
98.0
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_CONTAINER_LOWEST,
|
||||
surface_container_lowest,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
ContrastCurve::new(4.0, 4.0, 2.0, 0.0).get(s.contrast_level),
|
||||
100.0
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_CONTAINER_LOW,
|
||||
surface_container_low,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
ContrastCurve::new(10.0, 10.0, 11.0, 12.0).get(s.contrast_level),
|
||||
ContrastCurve::new(96.0, 96.0, 96.0, 95.0).get(s.contrast_level)
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_CONTAINER,
|
||||
surface_container,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
ContrastCurve::new(12.0, 12.0, 16.0, 20.0).get(s.contrast_level),
|
||||
ContrastCurve::new(94.0, 94.0, 92.0, 90.0).get(s.contrast_level)
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_CONTAINER_HIGH,
|
||||
surface_container_high,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
ContrastCurve::new(17.0, 17.0, 21.0, 25.0).get(s.contrast_level),
|
||||
ContrastCurve::new(92.0, 92.0, 88.0, 85.0).get(s.contrast_level)
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_CONTAINER_HIGHEST,
|
||||
surface_container_highest,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(
|
||||
s.is_dark,
|
||||
ContrastCurve::new(22.0, 22.0, 26.0, 30.0).get(s.contrast_level),
|
||||
ContrastCurve::new(90.0, 90.0, 84.0, 80.0).get(s.contrast_level)
|
||||
),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_SURFACE,
|
||||
on_surface,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 90.0, 10.0),
|
||||
highest_surface,
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
SURFACE_VARIANT,
|
||||
surface_variant,
|
||||
|s: &DynamicScheme| s.neutral_variant_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 30.0, 90.0),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_SURFACE_VARIANT,
|
||||
on_surface_variant,
|
||||
|s: &DynamicScheme| s.neutral_variant_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 80.0, 30.0),
|
||||
highest_surface,
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
INVERSE_SURFACE,
|
||||
inverse_surface,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 20.0, 95.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
INVERSE_ON_SURFACE,
|
||||
inverse_on_surface,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 20.0, 95.0),
|
||||
|_| Rc::clone(&INVERSE_SURFACE),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
OUTLINE,
|
||||
outline,
|
||||
|s: &DynamicScheme| s.neutral_variant_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 60.0, 50.0),
|
||||
highest_surface,
|
||||
ContrastCurve::new(1.5, 3.0, 4.5, 7.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
OUTLINE_VARIANT,
|
||||
outline_variant,
|
||||
|s: &DynamicScheme| s.neutral_variant_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 30.0, 80.0),
|
||||
highest_surface,
|
||||
ContrastCurve::new(1.0, 1.0, 3.0, 4.5)
|
||||
);
|
||||
dynamic_gen!(
|
||||
SHADOW,
|
||||
shadow,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|_| 0.0
|
||||
);
|
||||
dynamic_gen!(
|
||||
SCRIM,
|
||||
scrim,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|_| { 0.0 }
|
||||
);
|
||||
dynamic_gen!(
|
||||
PRIMARY,
|
||||
primary,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 100.0, 0.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 80.0, 40.0)
|
||||
}
|
||||
},
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&PRIMARY_CONTAINER),
|
||||
role_b: Rc::clone(&PRIMARY),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_PRIMARY,
|
||||
on_primary,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 10.0, 90.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 20.0, 100.0)
|
||||
}
|
||||
},
|
||||
|_| Rc::clone(&PRIMARY),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
PRIMARY_CONTAINER,
|
||||
primary_container,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_fidelity(s) {
|
||||
return s.source_color.l;
|
||||
}
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 85.0, 25.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 30.0, 90.0)
|
||||
}
|
||||
},
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&PRIMARY_CONTAINER),
|
||||
role_b: Rc::clone(&PRIMARY),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_PRIMARY_CONTAINER,
|
||||
on_primary_container,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_fidelity(s) {
|
||||
return foreground_tone(Rc::clone(&PRIMARY_CONTAINER).get_tone(s), 4.5);
|
||||
}
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 0.0, 100.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 90.0, 30.0)
|
||||
}
|
||||
},
|
||||
|_| Rc::clone(&PRIMARY_CONTAINER),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
INVERSE_PRIMARY,
|
||||
inverse_primary,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 40.0, 80.0),
|
||||
|_| Rc::clone(&INVERSE_SURFACE),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 7.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
SECONDARY,
|
||||
secondary,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 80.0, 40.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_SECONDARY,
|
||||
on_secondary,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 10.0, 100.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 20.0, 100.0)
|
||||
}
|
||||
},
|
||||
|_| Rc::clone(&SECONDARY),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
SECONDARY_CONTAINER,
|
||||
secondary_container,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
let initial_tone = cond!(s.is_dark, 30.0, 90.0);
|
||||
if is_monochrome(s) {
|
||||
return cond!(s.is_dark, 30.0, 85.0);
|
||||
}
|
||||
initial_tone
|
||||
},
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&SECONDARY_CONTAINER),
|
||||
role_b: Rc::clone(&SECONDARY),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_SECONDARY_CONTAINER,
|
||||
on_secondary_container,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
return cond!(s.is_dark, 90.0, 10.0);
|
||||
}
|
||||
if !is_fidelity(s) {
|
||||
return cond!(s.is_dark, 90.0, 30.0);
|
||||
}
|
||||
foreground_tone(Rc::clone(&SECONDARY_CONTAINER).get_tone(s), 4.5)
|
||||
},
|
||||
|_| Rc::clone(&SECONDARY_CONTAINER),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
TERTIARY,
|
||||
tertiary,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
return cond!(s.is_dark, 90.0, 25.0);
|
||||
}
|
||||
cond!(s.is_dark, 80.0, 40.0)
|
||||
},
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&TERTIARY_CONTAINER),
|
||||
role_b: Rc::clone(&TERTIARY),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_TERTIARY,
|
||||
on_tertiary,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
return cond!(s.is_dark, 10.0, 90.0);
|
||||
}
|
||||
cond!(s.is_dark, 20.0, 100.0)
|
||||
},
|
||||
|_| Rc::clone(&TERTIARY),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
TERTIARY_CONTAINER,
|
||||
tertiary_container,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
return cond!(s.is_dark, 60.0, 49.0);
|
||||
}
|
||||
if !is_fidelity(s) {
|
||||
return cond!(s.is_dark, 30.0, 90.0);
|
||||
}
|
||||
let proposed_lch = s.tertiary_palette.tone(s.source_color.l);
|
||||
fix_disliked(&proposed_lch).l
|
||||
},
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&TERTIARY_CONTAINER),
|
||||
role_b: Rc::clone(&TERTIARY),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_TERTIARY_CONTAINER,
|
||||
on_tertiary_container,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
return cond!(s.is_dark, 0.0, 100.0);
|
||||
}
|
||||
if !is_fidelity(s) {
|
||||
return cond!(s.is_dark, 90.0, 30.0);
|
||||
}
|
||||
foreground_tone(Rc::clone(&TERTIARY_CONTAINER).get_tone(s), 4.5)
|
||||
},
|
||||
|_| Rc::clone(&TERTIARY_CONTAINER),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
ERROR,
|
||||
error,
|
||||
|s: &DynamicScheme| s.error_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 80.0, 40.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&ERROR_CONTAINER),
|
||||
role_b: Rc::clone(&ERROR),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_ERROR,
|
||||
on_error,
|
||||
|s: &DynamicScheme| s.error_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 20.0, 100.0),
|
||||
|_| Rc::clone(&ERROR),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
ERROR_CONTAINER,
|
||||
error_container,
|
||||
|s: &DynamicScheme| s.error_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 30.0, 90.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&ERROR_CONTAINER),
|
||||
role_b: Rc::clone(&ERROR),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_ERROR_CONTAINER,
|
||||
on_error_container,
|
||||
|s: &DynamicScheme| s.error_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 90.0, 10.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 90.0, 30.0)
|
||||
}
|
||||
},
|
||||
|_| Rc::clone(&ERROR_CONTAINER),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
PRIMARY_FIXED,
|
||||
primary_fixed,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 40.0, 90.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&PRIMARY_FIXED),
|
||||
role_b: Rc::clone(&PRIMARY_FIXED_DIM),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
PRIMARY_FIXED_DIM,
|
||||
primary_fixed_dim,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 30.0, 80.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&PRIMARY_FIXED),
|
||||
role_b: Rc::clone(&PRIMARY_FIXED_DIM),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_PRIMARY_FIXED,
|
||||
on_primary_fixed,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 100.0, 10.0),
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED_DIM))),
|
||||
Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED))),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_PRIMARY_FIXED_VARIANT,
|
||||
on_primary_fixed_variant,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 90.0, 30.0),
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED_DIM))),
|
||||
Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED))),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
SECONDARY_FIXED,
|
||||
secondary_fixed,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 80.0, 90.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&SECONDARY_FIXED),
|
||||
role_b: Rc::clone(&SECONDARY_FIXED_DIM),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
SECONDARY_FIXED_DIM,
|
||||
secondary_fixed_dim,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 70.0, 80.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&SECONDARY_FIXED),
|
||||
role_b: Rc::clone(&SECONDARY_FIXED_DIM),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_SECONDARY_FIXED,
|
||||
on_secondary_fixed,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|_| 10.0,
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED_DIM))),
|
||||
Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED))),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_SECONDARY_FIXED_VARIANT,
|
||||
on_secondary_fixed_variant,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 25.0, 30.0),
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED_DIM))),
|
||||
Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED))),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
TERTIARY_FIXED,
|
||||
tertiary_fixed,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 40.0, 90.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&TERTIARY_FIXED),
|
||||
role_b: Rc::clone(&TERTIARY_FIXED_DIM),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
TERTIARY_FIXED_DIM,
|
||||
tertiary_fixed_dim,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 30.0, 80.0),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
Some(Box::new(|_| ToneDeltaPair {
|
||||
role_a: Rc::clone(&TERTIARY_FIXED),
|
||||
role_b: Rc::clone(&TERTIARY_FIXED_DIM),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_TERTIARY_FIXED,
|
||||
on_tertiary_fixed,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 100.0, 10.0),
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED_DIM))),
|
||||
Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED))),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
ON_TERTIARY_FIXED_VARIANT,
|
||||
on_tertiary_fixed_variant,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 90.0, 30.0),
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED_DIM))),
|
||||
Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED))),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None
|
||||
);
|
||||
|
||||
pub fn custom(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("{}", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s: &DynamicScheme| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 100.0, 0.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 80.0, 40.0)
|
||||
}
|
||||
}),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| ToneDeltaPair {
|
||||
role_a: custom_container(String::from(&name)),
|
||||
role_b: custom(String::from(&name)),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn on_custom(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("on_{}", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| {
|
||||
let custom_palette = s.custom_palettes.get(&name).unwrap().clone();
|
||||
custom_palette
|
||||
})
|
||||
},
|
||||
Box::new(|s: &DynamicScheme| {
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 10.0, 90.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 20.0, 100.0)
|
||||
}
|
||||
}),
|
||||
None,
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| custom(String::from(&name))))
|
||||
},
|
||||
None,
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn custom_container(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("{}_container", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s| {
|
||||
if is_fidelity(s) {
|
||||
return s.source_color.l;
|
||||
}
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 85.0, 25.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 30.0, 90.0)
|
||||
}
|
||||
}),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| ToneDeltaPair {
|
||||
role_a: custom_container(String::from(&name)),
|
||||
role_b: custom(String::from(&name)),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn on_custom_container(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("on_{}_container", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| {
|
||||
let custom_palette = s.custom_palettes.get(&name).unwrap().clone();
|
||||
custom_palette
|
||||
})
|
||||
},
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| {
|
||||
if is_fidelity(s) {
|
||||
return foreground_tone(custom_container(String::from(&name)).get_tone(s), 4.5);
|
||||
}
|
||||
if is_monochrome(s) {
|
||||
cond!(s.is_dark, 0.0, 100.0)
|
||||
} else {
|
||||
cond!(s.is_dark, 90.0, 30.0)
|
||||
}
|
||||
})
|
||||
},
|
||||
None,
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| custom_container(String::from(&name))))
|
||||
},
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn inverse_custom(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("inverse_{}", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s| cond!(s.is_dark, 20.0, 95.0)),
|
||||
None,
|
||||
Some(Box::new(|_| Rc::clone(&INVERSE_SURFACE))),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn custom_fixed(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("{}_fixed", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s| cond!(is_monochrome(s), 40.0, 90.0)),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| ToneDeltaPair {
|
||||
role_a: custom_fixed(String::from(&name)),
|
||||
role_b: custom_fixed_dim(String::from(&name)),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn custom_fixed_dim(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("{}_fixed_dim", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s| cond!(is_monochrome(s), 30.0, 80.0)),
|
||||
Some(true),
|
||||
Some(Box::new(highest_surface)),
|
||||
None,
|
||||
Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| ToneDeltaPair {
|
||||
role_a: custom_fixed(String::from(&name)),
|
||||
role_b: custom_fixed_dim(String::from(&name)),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn on_custom_fixed(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("on_{}_fixed", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s| cond!(is_monochrome(s), 100.0, 10.0)),
|
||||
None,
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| custom_fixed_dim(String::from(&name))))
|
||||
},
|
||||
Some(Box::new(move |_| custom_fixed(String::from(&name)))),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn on_custom_fixed_variant(name: String) -> Rc<DynamicColor> {
|
||||
Rc::new(DynamicColor::new(
|
||||
Some(&format!("on_{}_fixed_variant", name)),
|
||||
{
|
||||
let name = name.clone();
|
||||
Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
|
||||
},
|
||||
Box::new(|s| cond!(is_monochrome(s), 90.0, 30.0)),
|
||||
None,
|
||||
{
|
||||
let name = name.clone();
|
||||
Some(Box::new(move |_| custom_fixed_dim(String::from(&name))))
|
||||
},
|
||||
Some(Box::new(move |_| custom_fixed(String::from(&name)))),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None,
|
||||
))
|
||||
}
|
221
color-module/src/schemes/material_design_3_dynamic/mod.rs
Normal file
221
color-module/src/schemes/material_design_3_dynamic/mod.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use dynamic_scheme::DynamicScheme;
|
||||
use material_colors::*;
|
||||
use palette::{IntoColor, Lch, Srgb};
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
use crate::{cond, convert::map_lch_to_srgb_hex, errors};
|
||||
|
||||
use super::material_design_3::{M3BaselineColors, M3ColorSet, M3PaletteSwatch, M3SurfaceSet};
|
||||
pub use constants::Variant;
|
||||
|
||||
mod constants;
|
||||
mod contrast;
|
||||
mod contrast_curve;
|
||||
mod dynamic_color;
|
||||
mod dynamic_scheme;
|
||||
mod material_colors;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn material_design_3_dynamic_variant() -> Result<JsValue, String> {
|
||||
let variants = enum_iterator::all::<constants::Variant>()
|
||||
.map(|variant| {
|
||||
serde_json::json!({
|
||||
"label": variant.label(),
|
||||
"value": variant as u8,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&variants).map_err(|e| e.to_string())?)
|
||||
}
|
||||
|
||||
fn build_primary_color_set(scheme: &DynamicScheme) -> M3ColorSet {
|
||||
M3ColorSet {
|
||||
root: map_lch_to_srgb_hex(&PRIMARY.get_lch(scheme)),
|
||||
on_root: map_lch_to_srgb_hex(&ON_PRIMARY.get_lch(scheme)),
|
||||
container: map_lch_to_srgb_hex(&PRIMARY_CONTAINER.get_lch(scheme)),
|
||||
on_container: map_lch_to_srgb_hex(&ON_PRIMARY_CONTAINER.get_lch(scheme)),
|
||||
fixed: map_lch_to_srgb_hex(&PRIMARY_FIXED.get_lch(scheme)),
|
||||
on_fixed: map_lch_to_srgb_hex(&ON_PRIMARY_FIXED.get_lch(scheme)),
|
||||
fixed_variant: map_lch_to_srgb_hex(&ON_PRIMARY_FIXED_VARIANT.get_lch(scheme)),
|
||||
fixed_dim: map_lch_to_srgb_hex(&PRIMARY_FIXED_DIM.get_lch(scheme)),
|
||||
inverse: map_lch_to_srgb_hex(&INVERSE_PRIMARY.get_lch(scheme)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_secondary_color_set(scheme: &DynamicScheme) -> M3ColorSet {
|
||||
M3ColorSet {
|
||||
root: map_lch_to_srgb_hex(&SECONDARY.get_lch(scheme)),
|
||||
on_root: map_lch_to_srgb_hex(&ON_SECONDARY.get_lch(scheme)),
|
||||
container: map_lch_to_srgb_hex(&SECONDARY_CONTAINER.get_lch(scheme)),
|
||||
on_container: map_lch_to_srgb_hex(&ON_SECONDARY_CONTAINER.get_lch(scheme)),
|
||||
fixed: map_lch_to_srgb_hex(&SECONDARY_FIXED.get_lch(scheme)),
|
||||
on_fixed: map_lch_to_srgb_hex(&ON_SECONDARY_FIXED.get_lch(scheme)),
|
||||
fixed_variant: map_lch_to_srgb_hex(&ON_SECONDARY_FIXED_VARIANT.get_lch(scheme)),
|
||||
fixed_dim: map_lch_to_srgb_hex(&SECONDARY_FIXED_DIM.get_lch(scheme)),
|
||||
..cond!(
|
||||
scheme.is_dark,
|
||||
M3ColorSet::new_dark_set(&scheme.secondary_palette),
|
||||
M3ColorSet::new_light_set(&scheme.secondary_palette)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tertiary_color_set(scheme: &DynamicScheme) -> M3ColorSet {
|
||||
M3ColorSet {
|
||||
root: map_lch_to_srgb_hex(&TERTIARY.get_lch(scheme)),
|
||||
on_root: map_lch_to_srgb_hex(&ON_TERTIARY.get_lch(scheme)),
|
||||
container: map_lch_to_srgb_hex(&TERTIARY_CONTAINER.get_lch(scheme)),
|
||||
on_container: map_lch_to_srgb_hex(&ON_TERTIARY_CONTAINER.get_lch(scheme)),
|
||||
fixed: map_lch_to_srgb_hex(&TERTIARY_FIXED.get_lch(scheme)),
|
||||
on_fixed: map_lch_to_srgb_hex(&ON_TERTIARY_FIXED.get_lch(scheme)),
|
||||
fixed_variant: map_lch_to_srgb_hex(&ON_TERTIARY_FIXED_VARIANT.get_lch(scheme)),
|
||||
fixed_dim: map_lch_to_srgb_hex(&TERTIARY_FIXED_DIM.get_lch(scheme)),
|
||||
..cond!(
|
||||
scheme.is_dark,
|
||||
M3ColorSet::new_dark_set(&scheme.tertiary_palette),
|
||||
M3ColorSet::new_light_set(&scheme.tertiary_palette)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_surface_color_set(scheme: &DynamicScheme) -> M3SurfaceSet {
|
||||
M3SurfaceSet {
|
||||
root: map_lch_to_srgb_hex(&SURFACE.get_lch(scheme)),
|
||||
dim: map_lch_to_srgb_hex(&SURFACE_DIM.get_lch(scheme)),
|
||||
bright: map_lch_to_srgb_hex(&SURFACE_BRIGHT.get_lch(scheme)),
|
||||
variant: map_lch_to_srgb_hex(&SURFACE_VARIANT.get_lch(scheme)),
|
||||
container: map_lch_to_srgb_hex(&SURFACE_CONTAINER.get_lch(scheme)),
|
||||
container_lowest: map_lch_to_srgb_hex(&SURFACE_CONTAINER_LOWEST.get_lch(scheme)),
|
||||
container_low: map_lch_to_srgb_hex(&SURFACE_CONTAINER_LOW.get_lch(scheme)),
|
||||
container_high: map_lch_to_srgb_hex(&SURFACE_CONTAINER_HIGH.get_lch(scheme)),
|
||||
container_highest: map_lch_to_srgb_hex(&SURFACE_CONTAINER_HIGHEST.get_lch(scheme)),
|
||||
on_root: map_lch_to_srgb_hex(&ON_SURFACE.get_lch(scheme)),
|
||||
on_root_variant: map_lch_to_srgb_hex(&ON_SURFACE_VARIANT.get_lch(scheme)),
|
||||
inverse: map_lch_to_srgb_hex(&INVERSE_SURFACE.get_lch(scheme)),
|
||||
on_inverse: map_lch_to_srgb_hex(&INVERSE_ON_SURFACE.get_lch(scheme)),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_error_color_set(scheme: &DynamicScheme) -> M3ColorSet {
|
||||
M3ColorSet {
|
||||
root: map_lch_to_srgb_hex(&ERROR.get_lch(scheme)),
|
||||
on_root: map_lch_to_srgb_hex(&ON_ERROR.get_lch(scheme)),
|
||||
container: map_lch_to_srgb_hex(&ERROR_CONTAINER.get_lch(scheme)),
|
||||
on_container: map_lch_to_srgb_hex(&ON_ERROR_CONTAINER.get_lch(scheme)),
|
||||
..cond!(
|
||||
scheme.is_dark,
|
||||
M3ColorSet::new_dark_set(&scheme.error_palette),
|
||||
M3ColorSet::new_light_set(&scheme.error_palette)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn build_custom_color_set(scheme: &DynamicScheme, name: String) -> M3ColorSet {
|
||||
M3ColorSet {
|
||||
root: map_lch_to_srgb_hex(&custom(name.clone()).get_lch(scheme)),
|
||||
on_root: map_lch_to_srgb_hex(&on_custom(name.clone()).get_lch(scheme)),
|
||||
container: map_lch_to_srgb_hex(&custom_container(name.clone()).get_lch(scheme)),
|
||||
on_container: map_lch_to_srgb_hex(&on_custom_container(name.clone()).get_lch(scheme)),
|
||||
fixed: map_lch_to_srgb_hex(&custom_fixed(name.clone()).get_lch(scheme)),
|
||||
on_fixed: map_lch_to_srgb_hex(&on_custom_fixed(name.clone()).get_lch(scheme)),
|
||||
fixed_variant: map_lch_to_srgb_hex(&on_custom_fixed_variant(name.clone()).get_lch(scheme)),
|
||||
fixed_dim: map_lch_to_srgb_hex(&custom_fixed_dim(name.clone()).get_lch(scheme)),
|
||||
inverse: map_lch_to_srgb_hex(&inverse_custom(name.clone()).get_lch(scheme)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_baseline(scheme: &DynamicScheme) -> M3BaselineColors {
|
||||
M3BaselineColors::full_custom(
|
||||
build_primary_color_set(scheme),
|
||||
build_secondary_color_set(scheme),
|
||||
build_tertiary_color_set(scheme),
|
||||
build_error_color_set(scheme),
|
||||
build_surface_color_set(scheme),
|
||||
map_lch_to_srgb_hex(&OUTLINE.get_lch(scheme)),
|
||||
map_lch_to_srgb_hex(&OUTLINE_VARIANT.get_lch(scheme)),
|
||||
map_lch_to_srgb_hex(&SCRIM.get_lch(scheme)),
|
||||
map_lch_to_srgb_hex(&SHADOW.get_lch(scheme)),
|
||||
scheme
|
||||
.custom_palettes
|
||||
.keys()
|
||||
.map(|name| (name.clone(), build_custom_color_set(scheme, name.clone())))
|
||||
.collect(),
|
||||
scheme.is_dark,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_swatches(scheme: &DynamicScheme) -> HashMap<String, M3PaletteSwatch> {
|
||||
let mut swatches = HashMap::new();
|
||||
swatches.insert(
|
||||
"primary".to_string(),
|
||||
M3PaletteSwatch::new(&scheme.primary_palette),
|
||||
);
|
||||
swatches.insert(
|
||||
"secondary".to_string(),
|
||||
M3PaletteSwatch::new(&scheme.secondary_palette),
|
||||
);
|
||||
swatches.insert(
|
||||
"tertiary".to_string(),
|
||||
M3PaletteSwatch::new(&scheme.tertiary_palette),
|
||||
);
|
||||
swatches.insert(
|
||||
"error".to_string(),
|
||||
M3PaletteSwatch::new(&scheme.error_palette),
|
||||
);
|
||||
swatches.insert(
|
||||
"neutral".to_string(),
|
||||
M3PaletteSwatch::new(&scheme.neutral_palette),
|
||||
);
|
||||
swatches.insert(
|
||||
"neutral_variant".to_string(),
|
||||
M3PaletteSwatch::new(&scheme.neutral_variant_palette),
|
||||
);
|
||||
|
||||
for (name, palette) in &scheme.custom_palettes {
|
||||
swatches.insert(name.clone(), M3PaletteSwatch::new(palette));
|
||||
}
|
||||
|
||||
swatches
|
||||
}
|
||||
|
||||
pub fn build_dynamic_scheme(
|
||||
source_color: &str,
|
||||
error_color: Option<String>,
|
||||
custom_colors: HashMap<String, String>,
|
||||
variant: constants::Variant,
|
||||
contrast_level: f32,
|
||||
is_dark: bool,
|
||||
harmonize_customs: bool,
|
||||
) -> Result<DynamicScheme, errors::ColorError> {
|
||||
let source_color = Srgb::from_str(source_color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(source_color.to_string()))?
|
||||
.into_format::<f32>()
|
||||
.into_color();
|
||||
let error_color = error_color
|
||||
.map(|color| Srgb::from_str(&color))
|
||||
.transpose()
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB("error color".to_string()))?
|
||||
.map(|color| color.into_format::<f32>().into_color());
|
||||
let custom_colors = custom_colors
|
||||
.into_iter()
|
||||
.map(|(name, color)| {
|
||||
let color = Srgb::from_str(&color)
|
||||
.map_err(|_| errors::ColorError::UnrecogniazedRGB(color.clone()))?
|
||||
.into_format::<f32>()
|
||||
.into_color();
|
||||
Ok((name, color))
|
||||
})
|
||||
.collect::<Result<HashMap<String, Lch>, errors::ColorError>>()?;
|
||||
Ok(DynamicScheme::new(
|
||||
source_color,
|
||||
error_color,
|
||||
custom_colors,
|
||||
variant,
|
||||
contrast_level,
|
||||
is_dark,
|
||||
harmonize_customs,
|
||||
))
|
||||
}
|
@@ -1,24 +1,33 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use internment::Intern;
|
||||
use material_design_2::MaterialDesign2Scheme;
|
||||
use material_design_3::MaterialDesign3Scheme;
|
||||
use material_design_3_dynamic::{Variant, build_baseline, build_dynamic_scheme, build_swatches};
|
||||
use q_style::{QScheme, SchemeSetting};
|
||||
use swatch_style::{SwatchEntry, SwatchSchemeSetting};
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
use wasm_bindgen::{JsValue, prelude::wasm_bindgen};
|
||||
|
||||
use crate::errors;
|
||||
use crate::{errors, schemes::q_style_2::QScheme2};
|
||||
|
||||
pub mod material_design_2;
|
||||
pub mod material_design_3;
|
||||
pub mod material_design_3_dynamic;
|
||||
pub mod q_style;
|
||||
pub mod q_style_2;
|
||||
pub mod swatch_style;
|
||||
|
||||
pub trait SchemeExport {
|
||||
fn output_css_variables(&self) -> String;
|
||||
fn output_css_auto_scheme_variables(&self) -> String;
|
||||
fn output_scss_variables(&self) -> String;
|
||||
fn output_javascript_object(&self) -> String;
|
||||
}
|
||||
|
||||
pub fn get_static_str(s: &str) -> &'static str {
|
||||
Intern::new(s.to_string()).as_ref()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_material_design_3_scheme(
|
||||
source_color: &str,
|
||||
@@ -34,6 +43,7 @@ pub fn generate_material_design_3_scheme(
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
@@ -56,6 +66,7 @@ pub fn generate_material_design_2_scheme(
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
@@ -86,6 +97,7 @@ pub fn generate_q_scheme_automatically(
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
@@ -122,6 +134,51 @@ pub fn generate_q_scheme_manually(
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_q_scheme_2_manually(
|
||||
primary_color: &str,
|
||||
secondary_color: Option<String>,
|
||||
tertiary_color: Option<String>,
|
||||
accent_color: Option<String>,
|
||||
danger_color: &str,
|
||||
success_color: &str,
|
||||
warn_color: &str,
|
||||
info_color: &str,
|
||||
fg_color: &str,
|
||||
bg_color: &str,
|
||||
custom_colors: JsValue,
|
||||
setting: SchemeSetting,
|
||||
) -> Result<JsValue, errors::ColorError> {
|
||||
let mut scheme = QScheme2::new(
|
||||
primary_color,
|
||||
secondary_color.as_deref(),
|
||||
tertiary_color.as_deref(),
|
||||
accent_color.as_deref(),
|
||||
danger_color,
|
||||
success_color,
|
||||
warn_color,
|
||||
info_color,
|
||||
fg_color,
|
||||
bg_color,
|
||||
setting,
|
||||
)?;
|
||||
let custom_colors: HashMap<String, String> = serde_wasm_bindgen::from_value(custom_colors)
|
||||
.map_err(|_| errors::ColorError::UnableToParseArgument)?;
|
||||
for (name, color) in custom_colors {
|
||||
scheme.add_custom_color(&name, &color)?;
|
||||
}
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
@@ -137,6 +194,55 @@ pub fn generate_swatch_scheme(
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.swatches(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_material_design_3_dynamic_scheme(
|
||||
source_color: &str,
|
||||
error_color: Option<String>,
|
||||
variant: u8,
|
||||
contrast_level: f32,
|
||||
harmonize_customs: bool,
|
||||
custom_colors: JsValue,
|
||||
) -> Result<JsValue, errors::ColorError> {
|
||||
let custom_colors: HashMap<String, String> = serde_wasm_bindgen::from_value(custom_colors)
|
||||
.map_err(|_| errors::ColorError::UnableToParseArgument)?;
|
||||
let variant = Variant::from_u8(variant);
|
||||
|
||||
let light_scheme = build_dynamic_scheme(
|
||||
source_color,
|
||||
error_color.clone(),
|
||||
custom_colors.clone(),
|
||||
variant,
|
||||
contrast_level,
|
||||
false,
|
||||
harmonize_customs,
|
||||
)?;
|
||||
let dark_scheme = build_dynamic_scheme(
|
||||
source_color,
|
||||
error_color,
|
||||
custom_colors,
|
||||
variant,
|
||||
contrast_level,
|
||||
true,
|
||||
harmonize_customs,
|
||||
)?;
|
||||
|
||||
let scheme = MaterialDesign3Scheme::full_custom(
|
||||
build_baseline(&light_scheme),
|
||||
build_baseline(&dark_scheme),
|
||||
build_swatches(&light_scheme),
|
||||
);
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_css_auto_scheme_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
|
@@ -1,3 +1,4 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use palette::{
|
||||
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
|
||||
Oklch, ShiftHue,
|
||||
@@ -48,16 +49,19 @@ impl Baseline {
|
||||
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),
|
||||
primary: ColorSet::new(primary, &neutral_swatch, foreground.l, &setting),
|
||||
secondary: secondary
|
||||
.map(|color| ColorSet::new(&color, &neutral_swatch, foreground.l, &setting)),
|
||||
tertiary: tertiary
|
||||
.map(|color| ColorSet::new(&color, &neutral_swatch, foreground.l, &setting)),
|
||||
accent: accent
|
||||
.map(|color| ColorSet::new(&color, &neutral_swatch, foreground.l, &setting)),
|
||||
neutral: ColorSet::new(&neutral_color, &neutral_swatch, foreground.l, &setting),
|
||||
danger: ColorSet::new(danger, &neutral_swatch, foreground.l, &setting),
|
||||
success: ColorSet::new(success, &neutral_swatch, foreground.l, &setting),
|
||||
warning: ColorSet::new(warning, &neutral_swatch, foreground.l, &setting),
|
||||
info: ColorSet::new(info, &neutral_swatch, foreground.l, &setting),
|
||||
outline: ColorSet::new(&outline_color, &neutral_swatch, foreground.l, &setting),
|
||||
foreground: *foreground,
|
||||
background: *background,
|
||||
_neutral_swatch: neutral_swatch,
|
||||
@@ -155,6 +159,37 @@ impl Baseline {
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self) -> LinkedHashMap<String, String> {
|
||||
let mut collection = LinkedHashMap::new();
|
||||
|
||||
collection.extend(self.primary.to_css_auto_scheme_collection("primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
collection.extend(secondary.to_css_auto_scheme_collection("secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
collection.extend(tertiary.to_css_auto_scheme_collection("tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
collection.extend(accent.to_css_auto_scheme_collection("accent"));
|
||||
}
|
||||
collection.extend(self.neutral.to_css_auto_scheme_collection("neutral"));
|
||||
collection.extend(self.danger.to_css_auto_scheme_collection("danger"));
|
||||
collection.extend(self.success.to_css_auto_scheme_collection("success"));
|
||||
collection.extend(self.warning.to_css_auto_scheme_collection("warning"));
|
||||
collection.extend(self.info.to_css_auto_scheme_collection("info"));
|
||||
collection.extend(self.outline.to_css_auto_scheme_collection("outline"));
|
||||
collection.insert(
|
||||
"foreground".to_string(),
|
||||
map_oklch_to_srgb_hex(&self.foreground),
|
||||
);
|
||||
collection.insert(
|
||||
"background".to_string(),
|
||||
map_oklch_to_srgb_hex(&self.background),
|
||||
);
|
||||
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
|
@@ -1,3 +1,4 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use palette::{color_difference::Wcag21RelativeContrast, luma::Luma, Oklch};
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
@@ -24,34 +25,43 @@ pub struct ColorSet {
|
||||
|
||||
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;
|
||||
let mut fit_contrast = (f32::INFINITY, f32::NEG_INFINITY);
|
||||
let mut closest_contrast = (f32::INFINITY, f32::NEG_INFINITY);
|
||||
for scan_lightness in (0..=100).map(|x| x as f32 / 100.0) {
|
||||
let new_target = neutral_swatch.get(scan_lightness);
|
||||
let contrast_ratio = match_wacg(&new_target, &reference_luma);
|
||||
if (contrast_ratio - ratio).abs() < (closest_contrast.0 - ratio).abs()
|
||||
&& scan_lightness > closest_contrast.1
|
||||
{
|
||||
closest_contrast = (contrast_ratio, scan_lightness);
|
||||
}
|
||||
if contrast_ratio >= ratio
|
||||
&& (contrast_ratio - ratio).abs() < (closest_contrast.0 - ratio).abs()
|
||||
{
|
||||
fit_contrast = (contrast_ratio, scan_lightness);
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
neutral_swatch.get(if fit_contrast.0 == f32::INFINITY {
|
||||
closest_contrast.1
|
||||
} else {
|
||||
fit_contrast.1
|
||||
})
|
||||
}
|
||||
|
||||
impl ColorSet {
|
||||
pub fn new(color: &Oklch, neutral_swatch: &NeutralSwatch, setting: &SchemeSetting) -> Self {
|
||||
pub fn new(
|
||||
color: &Oklch,
|
||||
neutral_swatch: &NeutralSwatch,
|
||||
foreground_lightness: f32,
|
||||
setting: &SchemeSetting,
|
||||
) -> Self {
|
||||
let root = color.clone();
|
||||
let hover = color * setting.hover;
|
||||
let active = color * setting.active;
|
||||
@@ -60,11 +70,11 @@ impl ColorSet {
|
||||
|
||||
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),
|
||||
neutral_swatch.get(foreground_lightness),
|
||||
neutral_swatch.get(foreground_lightness),
|
||||
neutral_swatch.get(foreground_lightness),
|
||||
neutral_swatch.get(foreground_lightness),
|
||||
neutral_swatch.get(foreground_lightness),
|
||||
),
|
||||
WACGSetting::AutomaticAA => (
|
||||
fit_to_wacg(&root, neutral_swatch, 4.5),
|
||||
@@ -80,6 +90,13 @@ impl ColorSet {
|
||||
fit_to_wacg(&focus, neutral_swatch, 7.0),
|
||||
fit_to_wacg(&disabled, neutral_swatch, 7.0),
|
||||
),
|
||||
WACGSetting::HighContrast => (
|
||||
fit_to_wacg(&root, neutral_swatch, 21.0),
|
||||
fit_to_wacg(&hover, neutral_swatch, 21.0),
|
||||
fit_to_wacg(&active, neutral_swatch, 21.0),
|
||||
fit_to_wacg(&focus, neutral_swatch, 21.0),
|
||||
fit_to_wacg(&disabled, neutral_swatch, 21.0),
|
||||
),
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -163,6 +180,47 @@ impl ColorSet {
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut collection = LinkedHashMap::new();
|
||||
|
||||
collection.insert(format!("{}", name), map_oklch_to_srgb_hex(&self.root));
|
||||
collection.insert(
|
||||
format!("{}-hover", name),
|
||||
map_oklch_to_srgb_hex(&self.hover),
|
||||
);
|
||||
collection.insert(
|
||||
format!("{}-active", name),
|
||||
map_oklch_to_srgb_hex(&self.active),
|
||||
);
|
||||
collection.insert(
|
||||
format!("{}-focus", name),
|
||||
map_oklch_to_srgb_hex(&self.focus),
|
||||
);
|
||||
collection.insert(
|
||||
format!("{}-disabled", name),
|
||||
map_oklch_to_srgb_hex(&self.disabled),
|
||||
);
|
||||
collection.insert(format!("on-{}", name), map_oklch_to_srgb_hex(&self.on_root));
|
||||
collection.insert(
|
||||
format!("on-{}-hover", name),
|
||||
map_oklch_to_srgb_hex(&self.on_hover),
|
||||
);
|
||||
collection.insert(
|
||||
format!("on-{}-active", name),
|
||||
map_oklch_to_srgb_hex(&self.on_active),
|
||||
);
|
||||
collection.insert(
|
||||
format!("on-{}-focus", name),
|
||||
map_oklch_to_srgb_hex(&self.on_focus),
|
||||
);
|
||||
collection.insert(
|
||||
format!("on-{}-disabled", name),
|
||||
map_oklch_to_srgb_hex(&self.on_disabled),
|
||||
);
|
||||
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
|
@@ -1,10 +1,9 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use baseline::Baseline;
|
||||
use linked_hash_set::LinkedHashSet;
|
||||
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};
|
||||
@@ -16,7 +15,8 @@ mod color_set;
|
||||
mod neutral_swatch;
|
||||
mod scheme_setting;
|
||||
|
||||
pub use scheme_setting::{ColorShifting, SchemeSetting};
|
||||
pub use neutral_swatch::NeutralSwatch;
|
||||
pub use scheme_setting::{ColorExpand, ColorShifting, SchemeSetting, WACGSetting};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct QScheme {
|
||||
@@ -59,8 +59,8 @@ impl QScheme {
|
||||
&(success * setting.dark_convert),
|
||||
&(warning * setting.dark_convert),
|
||||
&(info * setting.dark_convert),
|
||||
&(foreground * setting.dark_convert),
|
||||
&(background * setting.dark_convert),
|
||||
&(&background * (setting.dark_convert / 2.0)),
|
||||
&(&foreground * setting.dark_convert),
|
||||
setting.clone(),
|
||||
),
|
||||
})
|
||||
@@ -113,7 +113,7 @@ impl QScheme {
|
||||
&(success * setting.dark_convert),
|
||||
&(warning * setting.dark_convert),
|
||||
&(info * setting.dark_convert),
|
||||
&(foreground * setting.dark_convert),
|
||||
&(foreground * (setting.dark_convert / 2.0)),
|
||||
&(background * setting.dark_convert),
|
||||
setting.clone(),
|
||||
),
|
||||
@@ -131,6 +131,32 @@ impl SchemeExport for QScheme {
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_css_auto_scheme_variables(&self) -> String {
|
||||
let mut collection = Vec::new();
|
||||
let mut keys = LinkedHashSet::new();
|
||||
let light_collection = self.light.to_css_auto_scheme_collection();
|
||||
let dark_collection = self.dark.to_css_auto_scheme_collection();
|
||||
|
||||
keys.extend(light_collection.keys().cloned());
|
||||
keys.extend(dark_collection.keys().cloned());
|
||||
for key in keys {
|
||||
match (light_collection.get(&key), dark_collection.get(&key)) {
|
||||
(Some(light), Some(dark)) => {
|
||||
collection.push(format!(
|
||||
"--color-{}: light-dark(#{}, #{});",
|
||||
key, light, dark
|
||||
));
|
||||
}
|
||||
(Some(color), None) | (None, Some(color)) => {
|
||||
collection.push(format!("--color-{}: #{}", key, color));
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
collection.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
@@ -169,11 +195,11 @@ impl SchemeExport for QScheme {
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn q_scheme_color_expanding_methods() -> Result<JsValue, String> {
|
||||
let methods = ColorExpand::iter()
|
||||
let methods = enum_iterator::all::<ColorExpand>()
|
||||
.map(|variant| {
|
||||
serde_json::json!({
|
||||
"label": variant.label(),
|
||||
"value": variant.to_string(),
|
||||
"value": variant as u8,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@@ -183,14 +209,19 @@ pub fn q_scheme_color_expanding_methods() -> Result<JsValue, String> {
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn q_scheme_wacg_settings() -> Result<JsValue, String> {
|
||||
let settings = WACGSetting::iter()
|
||||
let settings = enum_iterator::all::<WACGSetting>()
|
||||
.map(|setting| {
|
||||
serde_json::json!({
|
||||
"label": setting.label(),
|
||||
"value": setting.to_string(),
|
||||
"value": setting as u8,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
serde_wasm_bindgen::to_value(&settings).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn q_scheme_default_settings() -> SchemeSetting {
|
||||
SchemeSetting::default()
|
||||
}
|
||||
|
@@ -1,9 +1,11 @@
|
||||
use std::ops::Mul;
|
||||
use std::ops::{Div, Mul};
|
||||
|
||||
use enum_iterator::Sequence;
|
||||
use palette::Oklch;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{Display, EnumIter, EnumString};
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use strum::Display;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
@@ -12,6 +14,19 @@ pub struct ColorShifting {
|
||||
pub lightness: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ColorShifting {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(chroma: f32, lightness: f32) -> Self {
|
||||
ColorShifting { chroma, lightness }
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = toJsValue)]
|
||||
pub fn to_js_value(&self) -> Result<JsValue, JsError> {
|
||||
Ok(serde_wasm_bindgen::to_value(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<ColorShifting> for Oklch<f32> {
|
||||
type Output = Oklch<f32>;
|
||||
|
||||
@@ -21,13 +36,13 @@ impl Mul<ColorShifting> for Oklch<f32> {
|
||||
+ if rhs.lightness > 0.0 {
|
||||
(1.0 - self.l) * rhs.lightness
|
||||
} else {
|
||||
self.l * rhs.lightness * -1.0
|
||||
self.l * rhs.lightness
|
||||
},
|
||||
self.chroma
|
||||
+ if rhs.chroma > 0.0 {
|
||||
(100.0 - self.chroma) * rhs.chroma
|
||||
} else {
|
||||
-(self.chroma * rhs.chroma)
|
||||
self.chroma * rhs.chroma
|
||||
},
|
||||
self.hue,
|
||||
)
|
||||
@@ -43,19 +58,41 @@ impl Mul<ColorShifting> for &Oklch<f32> {
|
||||
+ if rhs.lightness > 0.0 {
|
||||
(1.0 - self.l) * rhs.lightness
|
||||
} else {
|
||||
self.l * rhs.lightness * -1.0
|
||||
self.l * rhs.lightness
|
||||
},
|
||||
self.chroma
|
||||
+ if rhs.chroma > 0.0 {
|
||||
(100.0 - self.chroma) * rhs.chroma
|
||||
} else {
|
||||
-(self.chroma * rhs.chroma)
|
||||
self.chroma * rhs.chroma
|
||||
},
|
||||
self.hue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for ColorShifting {
|
||||
type Output = ColorShifting;
|
||||
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
ColorShifting {
|
||||
chroma: self.chroma / rhs,
|
||||
lightness: self.lightness / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for &ColorShifting {
|
||||
type Output = ColorShifting;
|
||||
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
ColorShifting {
|
||||
chroma: self.chroma / rhs,
|
||||
lightness: self.lightness / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[wasm_bindgen]
|
||||
pub struct SchemeSetting {
|
||||
@@ -68,9 +105,9 @@ pub struct SchemeSetting {
|
||||
pub wacg_follows: WACGSetting,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Display, EnumString, EnumIter, Serialize, Deserialize)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[derive(Debug, Clone, Copy, Display, Sequence, Serialize_repr, Deserialize_repr)]
|
||||
#[wasm_bindgen]
|
||||
#[repr(u8)]
|
||||
pub enum ColorExpand {
|
||||
Complementary,
|
||||
Analogous,
|
||||
@@ -95,13 +132,14 @@ impl ColorExpand {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Display, EnumString, EnumIter, Serialize, Deserialize)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
#[derive(Debug, Clone, Copy, Display, Sequence, Serialize_repr, Deserialize_repr)]
|
||||
#[wasm_bindgen]
|
||||
#[repr(u8)]
|
||||
pub enum WACGSetting {
|
||||
Fixed,
|
||||
AutomaticAA,
|
||||
AutomaticAAA,
|
||||
HighContrast,
|
||||
}
|
||||
|
||||
impl WACGSetting {
|
||||
@@ -110,6 +148,7 @@ impl WACGSetting {
|
||||
WACGSetting::Fixed => "Fixed".to_string(),
|
||||
WACGSetting::AutomaticAA => "Automatic AA".to_string(),
|
||||
WACGSetting::AutomaticAAA => "Automatic AAA".to_string(),
|
||||
WACGSetting::HighContrast => "High Contrast".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,7 +170,7 @@ impl Default for SchemeSetting {
|
||||
},
|
||||
disabled: ColorShifting {
|
||||
chroma: -0.9,
|
||||
lightness: -0.2,
|
||||
lightness: 0.2,
|
||||
},
|
||||
dark_convert: ColorShifting {
|
||||
chroma: -0.3,
|
||||
@@ -142,3 +181,32 @@ impl Default for SchemeSetting {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SchemeSetting {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
hover: ColorShifting,
|
||||
active: ColorShifting,
|
||||
focus: ColorShifting,
|
||||
disabled: ColorShifting,
|
||||
dark_convert: ColorShifting,
|
||||
expand_method: ColorExpand,
|
||||
wacg_follows: WACGSetting,
|
||||
) -> Self {
|
||||
SchemeSetting {
|
||||
hover,
|
||||
active,
|
||||
focus,
|
||||
disabled,
|
||||
dark_convert,
|
||||
expand_method,
|
||||
wacg_follows,
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = toJsValue)]
|
||||
pub fn to_js_value(&self) -> Result<JsValue, JsError> {
|
||||
Ok(serde_wasm_bindgen::to_value(self)?)
|
||||
}
|
||||
}
|
||||
|
571
color-module/src/schemes/q_style_2/baseline.rs
Normal file
571
color-module/src/schemes/q_style_2/baseline.rs
Normal file
@@ -0,0 +1,571 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use palette::{
|
||||
Oklch, ShiftHue,
|
||||
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
|
||||
};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
convert::map_oklch_to_srgb_hex,
|
||||
errors,
|
||||
schemes::{
|
||||
q_style::{ColorExpand, NeutralSwatch, SchemeSetting},
|
||||
q_style_2::{
|
||||
color_set::ColorSet,
|
||||
swatch::{Swatch, generate_neutral_swatch_list},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorUnit {
|
||||
pub root: ColorSet,
|
||||
pub surface: ColorSet,
|
||||
pub swatch: Swatch,
|
||||
}
|
||||
|
||||
impl ColorUnit {
|
||||
pub fn new(
|
||||
color: &Oklch,
|
||||
neutral_swatch: &Arc<NeutralSwatch>,
|
||||
foreground_lightness: f32,
|
||||
settings: &Arc<SchemeSetting>,
|
||||
) -> Self {
|
||||
let root = ColorSet::new(color, neutral_swatch, foreground_lightness, settings);
|
||||
let surface = root.generate_surface_set();
|
||||
let swatch = root.generate_swatch();
|
||||
|
||||
Self {
|
||||
root,
|
||||
surface,
|
||||
swatch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
|
||||
css_variables.extend(self.root.to_css_variables(prefix, name));
|
||||
css_variables.extend(
|
||||
self.surface
|
||||
.to_css_variables(prefix, &format!("{name}-surface")),
|
||||
);
|
||||
css_variables.extend(self.swatch.to_css_variables(prefix, name));
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut css_auto_scheme_collection = LinkedHashMap::new();
|
||||
|
||||
css_auto_scheme_collection.extend(self.root.to_css_auto_scheme_collection(name));
|
||||
css_auto_scheme_collection.extend(
|
||||
self.surface
|
||||
.to_css_auto_scheme_collection(&format!("{name}-surface")),
|
||||
);
|
||||
css_auto_scheme_collection.extend(self.swatch.to_css_auto_scheme_collection(name));
|
||||
|
||||
css_auto_scheme_collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut scss_variables = Vec::new();
|
||||
|
||||
scss_variables.extend(self.root.to_scss_variables(prefix, name));
|
||||
scss_variables.extend(
|
||||
self.surface
|
||||
.to_scss_variables(prefix, &format!("{name}-surface")),
|
||||
);
|
||||
scss_variables.extend(self.swatch.to_scss_variables(prefix, name));
|
||||
|
||||
scss_variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_fields(&self, name: &str) -> Vec<String> {
|
||||
let mut js_object_fields = Vec::new();
|
||||
|
||||
js_object_fields.extend(self.root.to_javascript_fields(name));
|
||||
js_object_fields.extend(self.surface.to_javascript_fields(&format!("{name}Surface")));
|
||||
js_object_fields.extend(self.swatch.to_javascript_fields(name));
|
||||
|
||||
js_object_fields
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Baseline {
|
||||
pub primary: ColorUnit,
|
||||
pub secondary: Option<ColorUnit>,
|
||||
pub tertiary: Option<ColorUnit>,
|
||||
pub accent: Option<ColorUnit>,
|
||||
pub neutral: ColorSet,
|
||||
pub neutral_variant: ColorSet,
|
||||
pub surface: ColorSet,
|
||||
pub surface_variant: ColorSet,
|
||||
#[serde(serialize_with = "crate::schemes::q_style_2::swatch::serialize_neutral_swatch")]
|
||||
pub neutral_swatch: Arc<NeutralSwatch>,
|
||||
pub danger: ColorUnit,
|
||||
pub success: ColorUnit,
|
||||
pub warn: ColorUnit,
|
||||
pub info: ColorUnit,
|
||||
pub custom_colors: HashMap<String, ColorUnit>,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub shadow: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub overlay: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub outline: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub outline_variant: Oklch,
|
||||
#[serde(skip)]
|
||||
pub neutral_lightness: f32,
|
||||
#[serde(skip)]
|
||||
pub scheme_settings: Arc<SchemeSetting>,
|
||||
#[serde(skip)]
|
||||
pub is_dark: bool,
|
||||
}
|
||||
|
||||
impl Baseline {
|
||||
pub fn new(
|
||||
primary: &Oklch,
|
||||
secondary: Option<&Oklch>,
|
||||
tertiary: Option<&Oklch>,
|
||||
accent: Option<&Oklch>,
|
||||
danger: &Oklch,
|
||||
success: &Oklch,
|
||||
warn: &Oklch,
|
||||
info: &Oklch,
|
||||
neutral_lightest: &Oklch,
|
||||
neutral_darkest: &Oklch,
|
||||
settings: &Arc<SchemeSetting>,
|
||||
is_dark: bool,
|
||||
) -> Self {
|
||||
let (final_secondary, final_tertiary, final_accent) = match settings.expand_method {
|
||||
ColorExpand::Complementary => {
|
||||
let sec_color = secondary.cloned().or(Some(primary.complementary()));
|
||||
(sec_color, tertiary.cloned(), accent.cloned())
|
||||
}
|
||||
ColorExpand::Analogous => {
|
||||
let analogous_color = primary.analogous();
|
||||
(
|
||||
secondary.cloned().or(Some(analogous_color.0)),
|
||||
tertiary.cloned().or(Some(analogous_color.1)),
|
||||
accent.cloned(),
|
||||
)
|
||||
}
|
||||
ColorExpand::AnalogousAndComplementary => {
|
||||
let analogous_color = primary.analogous();
|
||||
let complementary_color = primary.complementary();
|
||||
(
|
||||
secondary.cloned().or(Some(analogous_color.0)),
|
||||
tertiary.cloned().or(Some(analogous_color.1)),
|
||||
accent.cloned().or(Some(complementary_color)),
|
||||
)
|
||||
}
|
||||
ColorExpand::Triadic => {
|
||||
let triadic_color = primary.triadic();
|
||||
(
|
||||
secondary.cloned().or(Some(triadic_color.0)),
|
||||
tertiary.cloned().or(Some(triadic_color.1)),
|
||||
accent.cloned(),
|
||||
)
|
||||
}
|
||||
ColorExpand::SplitComplementary => {
|
||||
let split_complementary_color = primary.split_complementary();
|
||||
(
|
||||
secondary.cloned().or(Some(split_complementary_color.0)),
|
||||
tertiary.cloned(),
|
||||
accent.cloned().or(Some(split_complementary_color.1)),
|
||||
)
|
||||
}
|
||||
ColorExpand::Tetradic => {
|
||||
let tetradic_color = primary.tetradic();
|
||||
(
|
||||
secondary.cloned().or(Some(tetradic_color.0)),
|
||||
tertiary.cloned().or(Some(tetradic_color.2)),
|
||||
accent.cloned().or(Some(tetradic_color.1)),
|
||||
)
|
||||
}
|
||||
ColorExpand::Square => {
|
||||
let c_90 = primary.shift_hue(90.0);
|
||||
let complementary_color = primary.complementary();
|
||||
let c_270 = primary.shift_hue(270.0);
|
||||
(
|
||||
secondary.cloned().or(Some(c_90)),
|
||||
tertiary.cloned().or(Some(c_270)),
|
||||
accent.cloned().or(Some(complementary_color)),
|
||||
)
|
||||
}
|
||||
};
|
||||
let reference_lightness = if is_dark {
|
||||
neutral_darkest.l
|
||||
} else {
|
||||
neutral_lightest.l
|
||||
};
|
||||
let neutral_swatch = Arc::new(NeutralSwatch::new(*neutral_lightest, *neutral_darkest));
|
||||
let outline_color = neutral_swatch.get(if is_dark { 0.25 } else { 0.7 });
|
||||
let outline_variant_color = neutral_swatch.get(if is_dark { 0.2 } else { 0.8 });
|
||||
let shadow_color = neutral_swatch.get(0.1);
|
||||
let overlay_color = neutral_swatch.get(0.3);
|
||||
|
||||
let neutral_color = neutral_swatch.get(if is_dark { 0.35 } else { 0.65 });
|
||||
let neutral_variant_color = neutral_swatch.get(if is_dark { 0.45 } else { 0.55 });
|
||||
let surface_color = neutral_swatch.get(if is_dark { 0.10 } else { 0.98 });
|
||||
let surface_variant_color = neutral_swatch.get(if is_dark { 0.20 } else { 0.85 });
|
||||
|
||||
let neutral_set = ColorSet::new(
|
||||
&neutral_color,
|
||||
&neutral_swatch,
|
||||
reference_lightness,
|
||||
settings,
|
||||
);
|
||||
let neutral_variant_set = ColorSet::new(
|
||||
&neutral_variant_color,
|
||||
&neutral_swatch,
|
||||
reference_lightness,
|
||||
settings,
|
||||
);
|
||||
let surface_set = ColorSet::new(
|
||||
&surface_color,
|
||||
&neutral_swatch,
|
||||
reference_lightness,
|
||||
settings,
|
||||
);
|
||||
let surface_variant_set = ColorSet::new(
|
||||
&surface_variant_color,
|
||||
&neutral_swatch,
|
||||
reference_lightness,
|
||||
settings,
|
||||
);
|
||||
|
||||
let primary_unit = ColorUnit::new(primary, &neutral_swatch, reference_lightness, settings);
|
||||
let secondary_unit = final_secondary
|
||||
.map(|color| ColorUnit::new(&color, &neutral_swatch, reference_lightness, settings));
|
||||
let tertiary_unit = final_tertiary
|
||||
.map(|color| ColorUnit::new(&color, &neutral_swatch, reference_lightness, settings));
|
||||
let accent_unit = final_accent
|
||||
.map(|color| ColorUnit::new(&color, &neutral_swatch, reference_lightness, settings));
|
||||
|
||||
let danger_unit = ColorUnit::new(danger, &neutral_swatch, reference_lightness, settings);
|
||||
let success_unit = ColorUnit::new(success, &neutral_swatch, reference_lightness, settings);
|
||||
let warn_unit = ColorUnit::new(warn, &neutral_swatch, reference_lightness, settings);
|
||||
let info_unit = ColorUnit::new(info, &neutral_swatch, reference_lightness, settings);
|
||||
|
||||
Self {
|
||||
primary: primary_unit,
|
||||
secondary: secondary_unit,
|
||||
tertiary: tertiary_unit,
|
||||
accent: accent_unit,
|
||||
neutral: neutral_set,
|
||||
neutral_variant: neutral_variant_set,
|
||||
surface: surface_set,
|
||||
surface_variant: surface_variant_set,
|
||||
neutral_swatch,
|
||||
danger: danger_unit,
|
||||
success: success_unit,
|
||||
warn: warn_unit,
|
||||
info: info_unit,
|
||||
custom_colors: HashMap::new(),
|
||||
shadow: shadow_color,
|
||||
overlay: overlay_color,
|
||||
outline: outline_color,
|
||||
outline_variant: outline_variant_color,
|
||||
neutral_lightness: reference_lightness,
|
||||
scheme_settings: settings.clone(),
|
||||
is_dark,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_custom_color(
|
||||
&mut self,
|
||||
name: &str,
|
||||
color: &Oklch,
|
||||
) -> Result<(), errors::ColorError> {
|
||||
let custom_color = ColorUnit::new(
|
||||
color,
|
||||
&self.neutral_swatch,
|
||||
self.neutral_lightness,
|
||||
&self.scheme_settings,
|
||||
);
|
||||
self.custom_colors.insert(name.to_string(), custom_color);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
let scheme_mode = if self.is_dark { "dark" } else { "light" };
|
||||
|
||||
css_variables.extend(self.primary.to_css_variables(scheme_mode, "primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
css_variables.extend(secondary.to_css_variables(scheme_mode, "secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
css_variables.extend(tertiary.to_css_variables(scheme_mode, "tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
css_variables.extend(accent.to_css_variables(scheme_mode, "accent"));
|
||||
}
|
||||
|
||||
css_variables.extend(self.danger.to_css_variables(scheme_mode, "danger"));
|
||||
css_variables.extend(self.success.to_css_variables(scheme_mode, "success"));
|
||||
css_variables.extend(self.warn.to_css_variables(scheme_mode, "warn"));
|
||||
css_variables.extend(self.info.to_css_variables(scheme_mode, "info"));
|
||||
css_variables.extend(self.neutral.to_css_variables(scheme_mode, "neutral"));
|
||||
css_variables.extend(
|
||||
self.neutral_variant
|
||||
.to_css_variables(scheme_mode, "neutral-variant"),
|
||||
);
|
||||
css_variables.extend(self.surface.to_css_variables(scheme_mode, "surface"));
|
||||
css_variables.extend(
|
||||
self.surface_variant
|
||||
.to_css_variables(scheme_mode, "surface-variant"),
|
||||
);
|
||||
|
||||
let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
|
||||
for (n, c) in neutral_swatch {
|
||||
css_variables.push(format!("--color-{scheme_mode}-swatch-neutral-{n}: #{c};"));
|
||||
}
|
||||
|
||||
css_variables.push(format!(
|
||||
"--color-{scheme_mode}-shadow: #{};",
|
||||
map_oklch_to_srgb_hex(&self.shadow)
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{scheme_mode}-overlay: #{};",
|
||||
map_oklch_to_srgb_hex(&self.overlay)
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{scheme_mode}-outlint: #{};",
|
||||
map_oklch_to_srgb_hex(&self.outline)
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{scheme_mode}-outline-variant: #{};",
|
||||
map_oklch_to_srgb_hex(&self.outline_variant)
|
||||
));
|
||||
|
||||
for (name, color_unit) in &self.custom_colors {
|
||||
let lowercased_name = name.to_lowercase();
|
||||
css_variables.extend(color_unit.to_css_variables(scheme_mode, &lowercased_name));
|
||||
}
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self) -> LinkedHashMap<String, String> {
|
||||
let mut css_variables = LinkedHashMap::new();
|
||||
|
||||
css_variables.extend(self.primary.to_css_auto_scheme_collection("primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
css_variables.extend(secondary.to_css_auto_scheme_collection("secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
css_variables.extend(tertiary.to_css_auto_scheme_collection("tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
css_variables.extend(accent.to_css_auto_scheme_collection("accent"));
|
||||
}
|
||||
css_variables.extend(self.danger.to_css_auto_scheme_collection("danger"));
|
||||
css_variables.extend(self.success.to_css_auto_scheme_collection("success"));
|
||||
css_variables.extend(self.warn.to_css_auto_scheme_collection("warn"));
|
||||
css_variables.extend(self.info.to_css_auto_scheme_collection("info"));
|
||||
css_variables.extend(self.neutral.to_css_auto_scheme_collection("neutral"));
|
||||
css_variables.extend(
|
||||
self.neutral_variant
|
||||
.to_css_auto_scheme_collection("neutral-variant"),
|
||||
);
|
||||
css_variables.extend(self.surface.to_css_auto_scheme_collection("surface"));
|
||||
css_variables.extend(
|
||||
self.surface_variant
|
||||
.to_css_auto_scheme_collection("surface-variant"),
|
||||
);
|
||||
|
||||
let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
|
||||
for (n, c) in neutral_swatch {
|
||||
css_variables.insert(format!("swatch-neutral-{n}"), c);
|
||||
}
|
||||
|
||||
css_variables.insert("shadow".to_string(), map_oklch_to_srgb_hex(&self.shadow));
|
||||
css_variables.insert("overlay".to_string(), map_oklch_to_srgb_hex(&self.overlay));
|
||||
css_variables.insert("outline".to_string(), map_oklch_to_srgb_hex(&self.outline));
|
||||
css_variables.insert(
|
||||
"outline-variant".to_string(),
|
||||
map_oklch_to_srgb_hex(&self.outline_variant),
|
||||
);
|
||||
|
||||
for (name, color) in &self.custom_colors {
|
||||
let lowercased_name = name.to_lowercase();
|
||||
css_variables.extend(color.to_css_auto_scheme_collection(&lowercased_name));
|
||||
}
|
||||
|
||||
css_variables
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self) -> Vec<String> {
|
||||
let mut scss_variables = Vec::new();
|
||||
let scheme_mode = if self.is_dark { "dark" } else { "light" };
|
||||
|
||||
scss_variables.extend(self.primary.to_scss_variables(scheme_mode, "primary"));
|
||||
if let Some(secondary) = &self.secondary {
|
||||
scss_variables.extend(secondary.to_scss_variables(scheme_mode, "secondary"));
|
||||
}
|
||||
if let Some(tertiary) = &self.tertiary {
|
||||
scss_variables.extend(tertiary.to_scss_variables(scheme_mode, "tertiary"));
|
||||
}
|
||||
if let Some(accent) = &self.accent {
|
||||
scss_variables.extend(accent.to_scss_variables(scheme_mode, "accent"));
|
||||
}
|
||||
scss_variables.extend(self.danger.to_scss_variables(scheme_mode, "danger"));
|
||||
scss_variables.extend(self.success.to_scss_variables(scheme_mode, "success"));
|
||||
scss_variables.extend(self.warn.to_scss_variables(scheme_mode, "warn"));
|
||||
scss_variables.extend(self.info.to_scss_variables(scheme_mode, "info"));
|
||||
|
||||
scss_variables.extend(self.neutral.to_scss_variables(scheme_mode, "neutral"));
|
||||
scss_variables.extend(
|
||||
self.neutral_variant
|
||||
.to_scss_variables(scheme_mode, "neutral-variant"),
|
||||
);
|
||||
scss_variables.extend(self.surface.to_scss_variables(scheme_mode, "surface"));
|
||||
scss_variables.extend(
|
||||
self.surface_variant
|
||||
.to_scss_variables(scheme_mode, "surface-variant"),
|
||||
);
|
||||
|
||||
let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
|
||||
for (n, c) in neutral_swatch {
|
||||
scss_variables.push(format!("$color-{scheme_mode}-swatch-neutral-{n}: #{c};"));
|
||||
}
|
||||
|
||||
scss_variables.push(format!(
|
||||
"$color-{scheme_mode}-shadow: #{};",
|
||||
map_oklch_to_srgb_hex(&self.shadow)
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{scheme_mode}-overlay: #{};",
|
||||
map_oklch_to_srgb_hex(&self.overlay)
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{scheme_mode}-outlint: #{};",
|
||||
map_oklch_to_srgb_hex(&self.outline)
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{scheme_mode}-outline-variant: #{};",
|
||||
map_oklch_to_srgb_hex(&self.outline_variant)
|
||||
));
|
||||
|
||||
for (name, color) in &self.custom_colors {
|
||||
let lowercased_name = name.to_lowercase();
|
||||
scss_variables.extend(color.to_scss_variables(scheme_mode, &lowercased_name));
|
||||
}
|
||||
|
||||
scss_variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_fields(&self) -> Vec<String> {
|
||||
let mut javascript_fields = Vec::new();
|
||||
let scheme_mode = if self.is_dark { "dark" } else { "light" };
|
||||
|
||||
javascript_fields.push(format!("{scheme_mode}: {{"));
|
||||
let indent = " ".repeat(4);
|
||||
|
||||
for line in self.primary.to_javascript_fields("primary").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self
|
||||
.secondary
|
||||
.as_ref()
|
||||
.map(|s| s.to_javascript_fields("secondary"))
|
||||
.unwrap_or(Vec::new())
|
||||
.iter()
|
||||
{
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self
|
||||
.tertiary
|
||||
.as_ref()
|
||||
.map(|s| s.to_javascript_fields("tertiary"))
|
||||
.unwrap_or(Vec::new())
|
||||
.iter()
|
||||
{
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self
|
||||
.accent
|
||||
.as_ref()
|
||||
.map(|s| s.to_javascript_fields("accent"))
|
||||
.unwrap_or(Vec::new())
|
||||
.iter()
|
||||
{
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
|
||||
for line in self.danger.to_javascript_fields("danger").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self.success.to_javascript_fields("success").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self.warn.to_javascript_fields("warn").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self.info.to_javascript_fields("info").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self.neutral.to_javascript_fields("neutral").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self
|
||||
.neutral_variant
|
||||
.to_javascript_fields("neutral_variant")
|
||||
.iter()
|
||||
{
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self.surface.to_javascript_fields("surface").iter() {
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
for line in self
|
||||
.surface_variant
|
||||
.to_javascript_fields("surface_variant")
|
||||
.iter()
|
||||
{
|
||||
javascript_fields.push(format!("{indent}{line:4}"));
|
||||
}
|
||||
|
||||
let neurtal_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
|
||||
for (n, c) in neurtal_swatch {
|
||||
javascript_fields.push(format!("{indent}neutralSwatch{n}: '#{c}',"));
|
||||
}
|
||||
|
||||
javascript_fields.push(format!(
|
||||
"{indent}shadow: '#{}',",
|
||||
map_oklch_to_srgb_hex(&self.shadow)
|
||||
));
|
||||
javascript_fields.push(format!(
|
||||
"{indent}overlay: '#{}',",
|
||||
map_oklch_to_srgb_hex(&self.overlay)
|
||||
));
|
||||
javascript_fields.push(format!(
|
||||
"{indent}outline: '#{}',",
|
||||
map_oklch_to_srgb_hex(&self.outline)
|
||||
));
|
||||
javascript_fields.push(format!(
|
||||
"{indent}outlineVariant: '#{}',",
|
||||
map_oklch_to_srgb_hex(&self.outline_variant)
|
||||
));
|
||||
|
||||
for (name, color) in &self.custom_colors {
|
||||
let lowercased_name = name.to_lowercase();
|
||||
let color_lines = color.to_javascript_fields(&lowercased_name);
|
||||
javascript_fields.extend(color_lines.iter().map(|s| format!("{indent}{s}")));
|
||||
}
|
||||
|
||||
javascript_fields.push("}".to_string());
|
||||
|
||||
javascript_fields
|
||||
}
|
||||
}
|
282
color-module/src/schemes/q_style_2/color_set.rs
Normal file
282
color-module/src/schemes/q_style_2/color_set.rs
Normal file
@@ -0,0 +1,282 @@
|
||||
use core::f32;
|
||||
use std::sync::Arc;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use palette::{Oklch, color_difference::Wcag21RelativeContrast, luma::Luma};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
convert::{map_oklch_to_luma, map_oklch_to_srgb_hex},
|
||||
schemes::{
|
||||
q_style::{NeutralSwatch, SchemeSetting, WACGSetting},
|
||||
q_style_2::swatch::Swatch,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ColorSet {
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub root: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub active: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub focus: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub hover: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub disabled: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub on_root: Oklch,
|
||||
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
|
||||
pub on_disabled: Oklch,
|
||||
#[serde(skip)]
|
||||
pub neutral_swatch: Arc<NeutralSwatch>,
|
||||
#[serde(skip)]
|
||||
pub neutral_lightness: f32,
|
||||
#[serde(skip)]
|
||||
pub scheme_settings: Arc<SchemeSetting>,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn match_wacg(original: &Oklch<f32>, reference: &Luma) -> f32 {
|
||||
let luma_original = map_oklch_to_luma(original);
|
||||
luma_original.relative_contrast(*reference)
|
||||
}
|
||||
|
||||
fn search_for_common_wacg_color(
|
||||
reference_colors: &[&Oklch],
|
||||
neutral_swatch: &NeutralSwatch,
|
||||
minium_ratio: f32,
|
||||
) -> Oklch {
|
||||
// store in: (lightness, avg_wacg_abs, sum_wacg_abs)
|
||||
let mut minium_match: (f32, f32, f32) = (0.0, f32::INFINITY, 0.0);
|
||||
let mut closest_match: (f32, f32, f32) = (f32::INFINITY, f32::INFINITY, 0.0);
|
||||
for scan_lightness in (0..=100).map(|x| x as f32 / 100.0) {
|
||||
let new_target = neutral_swatch.get(scan_lightness);
|
||||
let new_target_luma = map_oklch_to_luma(&new_target);
|
||||
let reference_wacgs_sum: f32 = reference_colors
|
||||
.iter()
|
||||
.map(|ref_color| match_wacg(&ref_color, &new_target_luma) - minium_ratio)
|
||||
.sum();
|
||||
let reference_wacgs = reference_wacgs_sum / reference_colors.len() as f32;
|
||||
if reference_wacgs.abs() < closest_match.1.abs() && reference_wacgs_sum > closest_match.2 {
|
||||
closest_match = (scan_lightness, reference_wacgs, reference_wacgs_sum);
|
||||
}
|
||||
if reference_wacgs >= 0.0
|
||||
&& reference_wacgs.abs() < minium_match.1.abs()
|
||||
&& reference_wacgs_sum > minium_match.2
|
||||
{
|
||||
minium_match = (scan_lightness, reference_wacgs, reference_wacgs_sum);
|
||||
}
|
||||
}
|
||||
if minium_match.1 != f32::INFINITY {
|
||||
neutral_swatch.get(minium_match.0)
|
||||
} else {
|
||||
neutral_swatch.get(closest_match.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorSet {
|
||||
pub fn new(
|
||||
color: &Oklch,
|
||||
neutral_swatch: &Arc<NeutralSwatch>,
|
||||
neutral_lightness: f32,
|
||||
settings: &Arc<SchemeSetting>,
|
||||
) -> Self {
|
||||
let neutral_swatch = Arc::clone(neutral_swatch);
|
||||
let settings = Arc::clone(settings);
|
||||
|
||||
let root = color.clone();
|
||||
let hover = color * settings.hover;
|
||||
let active = color * settings.active;
|
||||
let focus = color * settings.focus;
|
||||
let disabled = color * settings.disabled;
|
||||
|
||||
let color_list = &[&root, &hover, &active, &focus];
|
||||
|
||||
let (on_root, on_disabled) = match settings.wacg_follows {
|
||||
WACGSetting::Fixed => (
|
||||
neutral_swatch.get(neutral_lightness),
|
||||
neutral_swatch.get(neutral_lightness),
|
||||
),
|
||||
WACGSetting::AutomaticAA => (
|
||||
search_for_common_wacg_color(color_list, &neutral_swatch, 4.5),
|
||||
search_for_common_wacg_color(&[&disabled], &neutral_swatch, 4.5),
|
||||
),
|
||||
WACGSetting::AutomaticAAA => (
|
||||
search_for_common_wacg_color(color_list, &neutral_swatch, 7.0),
|
||||
search_for_common_wacg_color(&[&disabled], &neutral_swatch, 7.0),
|
||||
),
|
||||
WACGSetting::HighContrast => (
|
||||
search_for_common_wacg_color(color_list, &neutral_swatch, 21.0),
|
||||
search_for_common_wacg_color(&[&disabled], &neutral_swatch, 21.0),
|
||||
),
|
||||
};
|
||||
|
||||
Self {
|
||||
root,
|
||||
active,
|
||||
focus,
|
||||
hover,
|
||||
disabled,
|
||||
on_root,
|
||||
on_disabled,
|
||||
neutral_swatch,
|
||||
neutral_lightness,
|
||||
scheme_settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_surface_set(&self) -> Self {
|
||||
let root_swatch = Swatch::new(&self.root);
|
||||
let root_lightness = self.root.l;
|
||||
let surface_lightness = if root_lightness + 40.0 > 90.0 {
|
||||
root_lightness - 40.0
|
||||
} else {
|
||||
root_lightness + 40.0
|
||||
};
|
||||
let surface_color = root_swatch.get(surface_lightness);
|
||||
|
||||
Self::new(
|
||||
&surface_color,
|
||||
&self.neutral_swatch,
|
||||
self.neutral_lightness,
|
||||
&self.scheme_settings,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn generate_swatch(&self) -> Swatch {
|
||||
Swatch::new(&self.root)
|
||||
}
|
||||
|
||||
fn root_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.root)
|
||||
}
|
||||
|
||||
fn hover_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.hover)
|
||||
}
|
||||
|
||||
fn active_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.active)
|
||||
}
|
||||
|
||||
fn focus_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.focus)
|
||||
}
|
||||
|
||||
fn disabled_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.disabled)
|
||||
}
|
||||
|
||||
fn on_root_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.on_root)
|
||||
}
|
||||
|
||||
fn on_disabled_hex(&self) -> String {
|
||||
map_oklch_to_srgb_hex(&self.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}: #{};", self.root_hex()));
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-{name}-hover: #{};",
|
||||
self.hover_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-{name}-active: #{};",
|
||||
self.active_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-{name}-focus: #{};",
|
||||
self.focus_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-{name}-disabled: ${};",
|
||||
self.disabled_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-on-{name}: #{};",
|
||||
self.on_root_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-on-{name}-disabled: #{};",
|
||||
self.on_disabled_hex()
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut collection = LinkedHashMap::new();
|
||||
|
||||
collection.insert(format!("{name}"), self.root_hex());
|
||||
collection.insert(format!("{name}-hover"), self.hover_hex());
|
||||
collection.insert(format!("{name}-active"), self.active_hex());
|
||||
collection.insert(format!("{name}-focus"), self.focus_hex());
|
||||
collection.insert(format!("{name}-disabled"), self.disabled_hex());
|
||||
collection.insert(format!("on-{name}"), self.on_root_hex());
|
||||
collection.insert(format!("on-{name}-disabled"), self.on_disabled_hex());
|
||||
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.push(format!("&color-{prefix}-{name}: #{};", self.root_hex()));
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-{name}-hover: #{};",
|
||||
self.hover_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-{name}-active: #{};",
|
||||
self.active_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-{name}-focus: ${};",
|
||||
self.focus_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-{name}-disabled: #{};",
|
||||
self.disabled_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-on-{name}: #{};",
|
||||
self.on_root_hex()
|
||||
));
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-on-{name}-disabled: #{};",
|
||||
self.on_disabled_hex()
|
||||
));
|
||||
|
||||
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}: '#{}',", self.root_hex()));
|
||||
variables.push(format!("{name}Hover: '#{}',", self.hover_hex()));
|
||||
variables.push(format!("{name}Active: '#{}',", self.active_hex()));
|
||||
variables.push(format!("{name}Focus: '#{}',", self.focus_hex()));
|
||||
variables.push(format!("{name}Disabled: '#{}',", self.disabled_hex()));
|
||||
variables.push(format!("on{capitalized_name}: '#{}',", self.on_root_hex()));
|
||||
variables.push(format!(
|
||||
"on{capitalized_name}Disabled: '#{}',",
|
||||
self.on_disabled_hex()
|
||||
));
|
||||
|
||||
variables
|
||||
}
|
||||
}
|
157
color-module/src/schemes/q_style_2/mod.rs
Normal file
157
color-module/src/schemes/q_style_2/mod.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
use linked_hash_set::LinkedHashSet;
|
||||
use palette::FromColor;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
errors, parse_option_to_oklch, parse_to_oklch,
|
||||
schemes::{SchemeExport, q_style::SchemeSetting, q_style_2::baseline::Baseline},
|
||||
};
|
||||
|
||||
mod baseline;
|
||||
mod color_set;
|
||||
mod swatch;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct QScheme2 {
|
||||
pub light: Baseline,
|
||||
pub dark: Baseline,
|
||||
#[serde(skip)]
|
||||
_settings: Arc<SchemeSetting>,
|
||||
}
|
||||
|
||||
impl QScheme2 {
|
||||
pub fn new(
|
||||
primary: &str,
|
||||
secondary: Option<&str>,
|
||||
tertiary: Option<&str>,
|
||||
accent: Option<&str>,
|
||||
danger: &str,
|
||||
success: &str,
|
||||
warn: &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 warn = parse_to_oklch!(warn);
|
||||
let info = parse_to_oklch!(info);
|
||||
|
||||
let foreground = parse_to_oklch!(foreground);
|
||||
let background = parse_to_oklch!(background);
|
||||
|
||||
let settings = Arc::new(setting);
|
||||
|
||||
let light_scheme = Baseline::new(
|
||||
&primary,
|
||||
secondary.as_ref(),
|
||||
tertiary.as_ref(),
|
||||
accent.as_ref(),
|
||||
&danger,
|
||||
&success,
|
||||
&warn,
|
||||
&info,
|
||||
&foreground,
|
||||
&background,
|
||||
&settings,
|
||||
false,
|
||||
);
|
||||
let dark_scheme = Baseline::new(
|
||||
&(&primary * settings.dark_convert),
|
||||
secondary.map(|c| c * settings.dark_convert).as_ref(),
|
||||
tertiary.map(|c| c * settings.dark_convert).as_ref(),
|
||||
accent.map(|c| c * settings.dark_convert).as_ref(),
|
||||
&(danger * settings.dark_convert),
|
||||
&(success * settings.dark_convert),
|
||||
&(warn * settings.dark_convert),
|
||||
&(info * settings.dark_convert),
|
||||
&foreground,
|
||||
&background,
|
||||
&settings,
|
||||
true,
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
light: light_scheme,
|
||||
dark: dark_scheme,
|
||||
_settings: settings,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_custom_color(&mut self, name: &str, color: &str) -> Result<(), errors::ColorError> {
|
||||
let custom_color = parse_to_oklch!(color);
|
||||
|
||||
self.light.add_custom_color(name, &custom_color)?;
|
||||
self.dark
|
||||
.add_custom_color(name, &(custom_color * self._settings.dark_convert))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeExport for QScheme2 {
|
||||
fn output_css_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.light.to_css_variables());
|
||||
variables.extend(self.dark.to_css_variables());
|
||||
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_css_auto_scheme_variables(&self) -> String {
|
||||
let mut collection = Vec::new();
|
||||
let mut keys = LinkedHashSet::new();
|
||||
let light_collection = self.light.to_css_auto_scheme_collection();
|
||||
let dark_collection = self.dark.to_css_auto_scheme_collection();
|
||||
|
||||
keys.extend(light_collection.keys().cloned());
|
||||
keys.extend(dark_collection.keys().cloned());
|
||||
for key in keys {
|
||||
match (light_collection.get(&key), dark_collection.get(&key)) {
|
||||
(Some(light), Some(dark)) => {
|
||||
collection.push(format!("--color-{key}: light-dark(#{light}, #{dark});"));
|
||||
}
|
||||
(Some(color), None) | (None, Some(color)) => {
|
||||
collection.push(format!("--color-{key}: #{color}"));
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
|
||||
collection.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
variables.extend(self.light.to_scss_variables());
|
||||
variables.extend(self.dark.to_scss_variables());
|
||||
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_javascript_object(&self) -> String {
|
||||
let mut javascript_object = Vec::new();
|
||||
|
||||
let indent = " ".repeat(4);
|
||||
javascript_object.push("{".to_string());
|
||||
for line in self.light.to_javascript_fields() {
|
||||
javascript_object.push(format!("{indent}{line}"));
|
||||
}
|
||||
for line in self.dark.to_javascript_fields() {
|
||||
javascript_object.push(format!("{indent}{line}"));
|
||||
}
|
||||
javascript_object.push("}".to_string());
|
||||
|
||||
javascript_object.join("\n")
|
||||
}
|
||||
}
|
132
color-module/src/schemes/q_style_2/swatch.rs
Normal file
132
color-module/src/schemes/q_style_2/swatch.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use palette::Oklch;
|
||||
use serde::{Serialize, Serializer, ser::SerializeStruct};
|
||||
|
||||
use crate::{
|
||||
convert::map_oklch_to_srgb_hex,
|
||||
schemes::{get_static_str, q_style::NeutralSwatch},
|
||||
};
|
||||
|
||||
static SWATCH_LIGHTINGS: [u8; 16] = [
|
||||
10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 75, 80, 85, 90, 95, 98,
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Swatch(Oklch);
|
||||
|
||||
impl Swatch {
|
||||
pub fn new(color: &Oklch) -> Self {
|
||||
Self(color.clone())
|
||||
}
|
||||
|
||||
pub fn get<L: Into<f32>>(&self, lightness: L) -> Oklch {
|
||||
let request_lightness: f32 = lightness.into() / 100.0;
|
||||
Oklch {
|
||||
l: request_lightness.clamp(0.1, 0.98),
|
||||
..self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_hex<L: Into<f32>>(&self, lightness: L) -> String {
|
||||
let c = self.get(lightness.into());
|
||||
map_oklch_to_srgb_hex(&c)
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
variables.push(format!(
|
||||
"--color-{prefix}-swatch-{name}-{l:02}: #{};",
|
||||
self.get_hex(l)
|
||||
));
|
||||
}
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut collection = LinkedHashMap::new();
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
collection.insert(format!("swatch-{name}-{l:02}"), self.get_hex(l));
|
||||
}
|
||||
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
variables.push(format!(
|
||||
"$color-{prefix}-swatch-{name}-{l:02}: #{};",
|
||||
self.get_hex(l)
|
||||
));
|
||||
}
|
||||
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_javascript_fields(&self, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
variables.push(format!("{name}{l:02}: '#{}',", self.get_hex(l)));
|
||||
}
|
||||
|
||||
variables
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Swatch {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Swatch", SWATCH_LIGHTINGS.len())?;
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
let color = self.get_hex(l);
|
||||
let key: &'static str = get_static_str(&format!("{l:02}"));
|
||||
state.serialize_field(key, &color)?;
|
||||
}
|
||||
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_neutral_swatch_list(swatch: &Arc<NeutralSwatch>) -> LinkedHashMap<String, String> {
|
||||
let swatch = swatch.clone();
|
||||
let mut collection = LinkedHashMap::new();
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
let color = swatch.get((l as f32) / 100.0);
|
||||
let color = map_oklch_to_srgb_hex(&color);
|
||||
collection.insert(format!("{l:02}"), color);
|
||||
}
|
||||
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn serialize_neutral_swatch<S>(
|
||||
swatch: &Arc<NeutralSwatch>,
|
||||
serailizer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let swatch = swatch.clone();
|
||||
let mut swatch_struct = serailizer.serialize_struct("NeutralSwatch", SWATCH_LIGHTINGS.len())?;
|
||||
|
||||
for l in SWATCH_LIGHTINGS {
|
||||
let color = swatch.get((l as f32) / 100.0);
|
||||
let color = map_oklch_to_srgb_hex(&color);
|
||||
let key: &'static str = get_static_str(&format!("{l:02}"));
|
||||
swatch_struct.serialize_field(key, &color)?;
|
||||
}
|
||||
|
||||
swatch_struct.end()
|
||||
}
|
@@ -1,4 +1,7 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use linked_hash_set::LinkedHashSet;
|
||||
use palette::FromColor;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
@@ -19,13 +22,29 @@ pub struct SwatchScheme {
|
||||
dark: HashMap<String, Swatch>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub struct SwatchEntry {
|
||||
pub name: String,
|
||||
pub color: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SwatchEntry {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(name: &str, color: &str) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
color: color.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = toJsValue)]
|
||||
pub fn to_js_value(&self) -> Result<JsValue, JsError> {
|
||||
Ok(serde_wasm_bindgen::to_value(self)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl SwatchScheme {
|
||||
pub fn new(
|
||||
colors: Vec<SwatchEntry>,
|
||||
@@ -67,23 +86,56 @@ impl SchemeExport for SwatchScheme {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
for (name, swatch) in &self.light {
|
||||
variables.extend(swatch.to_css_variables("light", name));
|
||||
variables.extend(swatch.to_css_variables("light", &name.to_lowercase()));
|
||||
}
|
||||
for (name, swatch) in &self.dark {
|
||||
variables.extend(swatch.to_css_variables("dark", name));
|
||||
variables.extend(swatch.to_css_variables("dark", &name.to_lowercase()));
|
||||
}
|
||||
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_css_auto_scheme_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
let mut keys = LinkedHashSet::new();
|
||||
let mut light_collections = LinkedHashMap::new();
|
||||
let mut dark_collections = LinkedHashMap::new();
|
||||
|
||||
for (name, swatch) in &self.light {
|
||||
light_collections.extend(swatch.to_css_auto_scheme_collection(&name.to_lowercase()));
|
||||
}
|
||||
for (name, swatch) in &self.dark {
|
||||
dark_collections.extend(swatch.to_css_auto_scheme_collection(&name.to_lowercase()));
|
||||
}
|
||||
|
||||
keys.extend(light_collections.keys().cloned());
|
||||
keys.extend(dark_collections.keys().cloned());
|
||||
|
||||
for key in keys.iter() {
|
||||
match (light_collections.get(key), dark_collections.get(key)) {
|
||||
(Some(light), Some(dark)) => {
|
||||
variables.push(format!(
|
||||
"--color-{}: light-dark(#{}, #{});",
|
||||
key, light, dark
|
||||
));
|
||||
}
|
||||
(Some(color), None) | (None, Some(color)) => {
|
||||
variables.push(format!("--color-{}: #{};", key, color));
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
}
|
||||
variables.join("\n")
|
||||
}
|
||||
|
||||
fn output_scss_variables(&self) -> String {
|
||||
let mut variables = Vec::new();
|
||||
|
||||
for (name, swatch) in &self.light {
|
||||
variables.extend(swatch.to_scss_variables("light", name));
|
||||
variables.extend(swatch.to_scss_variables("light", &name.to_lowercase()));
|
||||
}
|
||||
for (name, swatch) in &self.dark {
|
||||
variables.extend(swatch.to_scss_variables("dark", name));
|
||||
variables.extend(swatch.to_scss_variables("dark", &name.to_lowercase()));
|
||||
}
|
||||
|
||||
variables.join("\n")
|
||||
@@ -97,7 +149,7 @@ impl SchemeExport for SwatchScheme {
|
||||
for (name, swatch) in &self.light {
|
||||
object.extend(
|
||||
swatch
|
||||
.to_javascript_fields("light", name)
|
||||
.to_javascript_fields("light", &name.to_lowercase())
|
||||
.iter()
|
||||
.map(|s| format!(" {}", s)),
|
||||
);
|
||||
@@ -108,7 +160,7 @@ impl SchemeExport for SwatchScheme {
|
||||
for (name, swatch) in &self.dark {
|
||||
object.extend(
|
||||
swatch
|
||||
.to_javascript_fields("dark", name)
|
||||
.to_javascript_fields("dark", &name.to_lowercase())
|
||||
.iter()
|
||||
.map(|s| format!(" {}", s)),
|
||||
);
|
||||
@@ -120,3 +172,8 @@ impl SchemeExport for SwatchScheme {
|
||||
object.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn swatch_scheme_default_settings() -> SwatchSchemeSetting {
|
||||
SwatchSchemeSetting::default()
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
use serde::Serialize;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue};
|
||||
|
||||
use crate::schemes::q_style::ColorShifting;
|
||||
|
||||
@@ -17,8 +17,8 @@ impl Default for SwatchSchemeSetting {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
amount: 10,
|
||||
min_lightness: 10.0,
|
||||
max_lightness: 90.0,
|
||||
min_lightness: 0.1,
|
||||
max_lightness: 0.9,
|
||||
include_primary: false,
|
||||
dark_convert: ColorShifting {
|
||||
chroma: -0.3,
|
||||
@@ -27,3 +27,28 @@ impl Default for SwatchSchemeSetting {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl SwatchSchemeSetting {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
amount: usize,
|
||||
min_lightness: f32,
|
||||
max_lightness: f32,
|
||||
include_primary: bool,
|
||||
dark_convert: ColorShifting,
|
||||
) -> Self {
|
||||
Self {
|
||||
amount,
|
||||
min_lightness,
|
||||
max_lightness,
|
||||
include_primary,
|
||||
dark_convert,
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = toJsValue)]
|
||||
pub fn to_js_value(&self) -> Result<JsValue, JsError> {
|
||||
Ok(serde_wasm_bindgen::to_value(self)?)
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
use linked_hash_map::LinkedHashMap;
|
||||
use palette::Oklch;
|
||||
|
||||
use crate::convert::map_oklch_to_srgb_hex;
|
||||
@@ -24,28 +25,26 @@ impl Swatch {
|
||||
}
|
||||
}
|
||||
|
||||
fn find_interval(&self) -> (usize, usize) {
|
||||
fn find_interval(&self) -> usize {
|
||||
if !self.include_primary {
|
||||
return (0, 0);
|
||||
return 0;
|
||||
}
|
||||
if self.primary_key.l == self.min_key {
|
||||
return (0, 1);
|
||||
return 0;
|
||||
}
|
||||
if self.primary_key.l == self.max_key {
|
||||
return (self.color_amount - 2, self.color_amount - 1);
|
||||
return self.color_amount - 1;
|
||||
}
|
||||
let step = (self.max_key - self.min_key) / (self.color_amount - 1) as f32;
|
||||
let index = ((self.primary_key.l - self.min_key) / step) as usize;
|
||||
|
||||
(index, index + 1)
|
||||
((self.primary_key.l - self.min_key) / step).ceil() as usize
|
||||
}
|
||||
|
||||
pub fn swatch(&self) -> Vec<Oklch> {
|
||||
let mut swatch = Vec::new();
|
||||
if self.include_primary {
|
||||
let (_, primary_index) = self.find_interval();
|
||||
let primary_index = self.find_interval();
|
||||
if primary_index > 0 {
|
||||
let step = (self.max_key - self.min_key) / primary_index as f32;
|
||||
let step = (self.primary_key.l - self.min_key) / (primary_index - 1) as f32;
|
||||
for i in 0..primary_index {
|
||||
let lightness = self.min_key + step * i as f32;
|
||||
swatch.push(Oklch {
|
||||
@@ -55,8 +54,8 @@ impl Swatch {
|
||||
}
|
||||
}
|
||||
if primary_index < self.color_amount - 1 {
|
||||
let step =
|
||||
(self.max_key - self.min_key) / (self.color_amount - primary_index) as f32;
|
||||
let step = (self.max_key - self.primary_key.l)
|
||||
/ (self.color_amount - primary_index - 1) as f32;
|
||||
for i in primary_index..self.color_amount {
|
||||
let lightness = self.min_key + step * i as f32;
|
||||
swatch.push(Oklch {
|
||||
@@ -96,6 +95,17 @@ impl Swatch {
|
||||
variables
|
||||
}
|
||||
|
||||
pub fn to_css_auto_scheme_collection(&self, name: &str) -> LinkedHashMap<String, String> {
|
||||
let mut collection = LinkedHashMap::new();
|
||||
for (i, color) in self.swatch().iter().enumerate() {
|
||||
collection.insert(
|
||||
format!("{}-{}", name, i * 100),
|
||||
map_oklch_to_srgb_hex(color),
|
||||
);
|
||||
}
|
||||
collection
|
||||
}
|
||||
|
||||
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||
let mut variables = Vec::new();
|
||||
for (i, color) in self.swatch().iter().enumerate() {
|
||||
|
@@ -106,3 +106,33 @@ pub fn triadic(color: &str) -> Result<Vec<String>, errors::ColorError> {
|
||||
format!("{:x}", srgb_p240.into_format::<u8>()),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn sanitize_hue_degrees(degrees: f32) -> f32 {
|
||||
let degrees = degrees % 360.0;
|
||||
if degrees < 0.0 {
|
||||
degrees + 360.0
|
||||
} else {
|
||||
degrees
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difference_in_degrees(a: f32, b: f32) -> f32 {
|
||||
180.0 - ((a - b).abs() - 180.0).abs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rotation_direction(from: f32, to: f32) -> f32 {
|
||||
let difference = sanitize_hue_degrees(to - from);
|
||||
if difference <= 180.0 {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn harmonize_hue(design_hue: f32, source_hue: f32) -> f32 {
|
||||
let difference = difference_in_degrees(design_hue, source_hue);
|
||||
let rotation_degrees = (difference * 0.5).min(15.0);
|
||||
sanitize_hue_degrees(design_hue + rotation_degrees * rotation_direction(design_hue, source_hue))
|
||||
}
|
||||
|
25
index.html
25
index.html
@@ -1,13 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description"
|
||||
content="By transforming and selecting various color theories, freely design UI color combinations." />
|
||||
<title>Color Lab</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
6
logo.svg
Normal file
6
logo.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="200px" height="200px" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs/>
|
||||
<circle cx="100" cy="100" r="80" fill="none" stroke-width="20" style="stroke: rgb(235, 97, 255);"/>
|
||||
<circle cx="132" cy="132" r="16" fill="rgb(235, 97, 255)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 289 B |
11
package.json
11
package.json
@@ -10,14 +10,15 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/react": "^5.1.0",
|
||||
"@iconify/react": "^6.0.0",
|
||||
"clsx": "^2.1.1",
|
||||
"color-module": "./color_functions",
|
||||
"dayjs": "^1.11.13",
|
||||
"jotai": "^2.11.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-error-boundary": "^5.0.0",
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"react-use": "^17.6.0",
|
||||
@@ -35,10 +36,10 @@
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"globals": "^16.3.0",
|
||||
"lightningcss": "^1.28.2",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
import init, * as funcs from 'color-module';
|
||||
import { createContext, ReactNode, use, useEffect, useMemo, useState, useTransition } from 'react';
|
||||
import init, * as funcs from './color_functions/color_module';
|
||||
|
||||
export type ColorFunctionContextType = {
|
||||
colorFn: typeof funcs | null;
|
||||
@@ -23,7 +23,7 @@ export function useColorFunction(): ColorFunctionContextType {
|
||||
}
|
||||
|
||||
export function ColorFunctionProvider({ children }: WasmProviderProps) {
|
||||
const [wasmInstance, setWasmInstance] = useState<Wasm.InitOutput | null>(null);
|
||||
const [wasmInstance, setWasmInstance] = useState<typeof funcs | null>(null);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
|
300
src/color_functions/color_module.d.ts
vendored
300
src/color_functions/color_module.d.ts
vendored
@@ -1,300 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export function q_scheme_color_expanding_methods(): any;
|
||||
export function q_scheme_wacg_settings(): any;
|
||||
export function differ_in_rgb(color: string, other: string): RGBDifference;
|
||||
export function relative_differ_in_rgb(color: string, other: string): RGBDifference;
|
||||
export function differ_in_hsl(color: string, other: string): HSLDifference;
|
||||
export function relative_differ_in_hsl(color: string, other: string): HSLDifference;
|
||||
export function differ_in_hct(color: string, other: string): HctDiffference;
|
||||
export function relative_differ_in_hct(color: string, other: string): HctDiffference;
|
||||
export function differ_in_oklch(color: string, other: string): OklchDifference;
|
||||
export function relative_differ_in_oklch(color: string, other: string): OklchDifference;
|
||||
export function tint_scale(basic_color: string, mixed_color: string): MixReversing;
|
||||
export function shade_scale(basic_color: string, mixed_color: string): MixReversing;
|
||||
export function shift_hue(color: string, degree: number): string;
|
||||
export function analogous_30(color: string): (string)[];
|
||||
export function analogous_60(color: string): (string)[];
|
||||
export function complementary(color: string): string;
|
||||
export function split_complementary(color: string): (string)[];
|
||||
export function tetradic(color: string): (string)[];
|
||||
export function triadic(color: string): (string)[];
|
||||
export function generate_palette_from_color(reference_color: string, swatch_amount: number, minimum_lightness: number, maximum_lightness: number, use_reference_color?: boolean, reference_color_bias?: number): (string)[];
|
||||
export function color_categories(): any;
|
||||
export function search_color_cards(tag: string, category?: string): any;
|
||||
export function generate_material_design_3_scheme(source_color: string, error_color: string, custom_colors: any): any;
|
||||
export function generate_material_design_2_scheme(primary_color: string, secondary_color: string, error_color: string, custom_colors: any): any;
|
||||
export function generate_q_scheme_automatically(primary_color: string, danger_color: string, success_color: string, warning_color: string, info_color: string, fg_color: string, bg_color: string, setting: SchemeSetting): any;
|
||||
export function generate_q_scheme_manually(primary_color: string, secondary_color: string | undefined, tertiary_color: string | undefined, accent_color: string | undefined, danger_color: string, success_color: string, warning_color: string, info_color: string, fg_color: string, bg_color: string, setting: SchemeSetting): any;
|
||||
export function generate_swatch_scheme(colors: (SwatchEntry)[], setting: SwatchSchemeSetting): any;
|
||||
export function lighten(color: string, percent: number): string;
|
||||
export function lighten_absolute(color: string, value: number): string;
|
||||
export function darken(color: string, percent: number): string;
|
||||
export function darken_absolute(color: string, value: number): string;
|
||||
export function mix(color1: string, color2: string, percent: number): string;
|
||||
export function tint(color: string, percent: number): string;
|
||||
export function shade(color: string, percent: number): string;
|
||||
export function series(color: string, expand_amount: number, step: number): (string)[];
|
||||
export function tonal_lighten_series(color: string, expand_amount: number, step: number): (string)[];
|
||||
export function tonal_darken_series(color: string, expand_amount: number, step: number): (string)[];
|
||||
export function represent_rgb(color: string): Uint8Array;
|
||||
export function rgb_to_hex(r: number, g: number, b: number): string;
|
||||
export function represent_hsl(color: string): Float32Array;
|
||||
export function hsl_to_hex(h: number, s: number, l: number): string;
|
||||
export function represent_lab(color: string): Float32Array;
|
||||
export function lab_to_hex(l: number, a: number, b: number): string;
|
||||
export function represent_oklch(color: string): Float32Array;
|
||||
export function oklch_to_hex(l: number, c: number, h: number): string;
|
||||
export function represent_hct(color: string): Float32Array;
|
||||
export function hct_to_hex(hue: number, chroma: number, tone: number): string;
|
||||
export function wacg_relative_contrast(fg_color: string, bg_color: string): number;
|
||||
export enum ColorExpand {
|
||||
Complementary = 0,
|
||||
Analogous = 1,
|
||||
AnalogousAndComplementary = 2,
|
||||
Triadic = 3,
|
||||
SplitComplementary = 4,
|
||||
Tetradic = 5,
|
||||
Square = 6,
|
||||
}
|
||||
export enum WACGSetting {
|
||||
Fixed = 0,
|
||||
AutomaticAA = 1,
|
||||
AutomaticAAA = 2,
|
||||
}
|
||||
export class ColorShifting {
|
||||
private constructor();
|
||||
free(): void;
|
||||
chroma: number;
|
||||
lightness: number;
|
||||
}
|
||||
export class Differ {
|
||||
private constructor();
|
||||
free(): void;
|
||||
delta: number;
|
||||
percent: number;
|
||||
}
|
||||
export class HSLDifference {
|
||||
private constructor();
|
||||
free(): void;
|
||||
hue: Differ;
|
||||
saturation: Differ;
|
||||
lightness: Differ;
|
||||
}
|
||||
export class HctDiffference {
|
||||
private constructor();
|
||||
free(): void;
|
||||
hue: Differ;
|
||||
chroma: Differ;
|
||||
lightness: Differ;
|
||||
}
|
||||
export class MixReversing {
|
||||
private constructor();
|
||||
free(): void;
|
||||
r_factor: number;
|
||||
g_factor: number;
|
||||
b_factor: number;
|
||||
average: number;
|
||||
}
|
||||
export class OklchDifference {
|
||||
private constructor();
|
||||
free(): void;
|
||||
hue: Differ;
|
||||
chroma: Differ;
|
||||
lightness: Differ;
|
||||
}
|
||||
export class RGBDifference {
|
||||
private constructor();
|
||||
free(): void;
|
||||
r: Differ;
|
||||
g: Differ;
|
||||
b: Differ;
|
||||
}
|
||||
export class SchemeSetting {
|
||||
private constructor();
|
||||
free(): void;
|
||||
hover: ColorShifting;
|
||||
active: ColorShifting;
|
||||
focus: ColorShifting;
|
||||
disabled: ColorShifting;
|
||||
dark_convert: ColorShifting;
|
||||
expand_method: ColorExpand;
|
||||
wacg_follows: WACGSetting;
|
||||
}
|
||||
export class SwatchEntry {
|
||||
private constructor();
|
||||
free(): void;
|
||||
name: string;
|
||||
color: string;
|
||||
}
|
||||
export class SwatchSchemeSetting {
|
||||
private constructor();
|
||||
free(): void;
|
||||
amount: number;
|
||||
min_lightness: number;
|
||||
max_lightness: number;
|
||||
include_primary: boolean;
|
||||
dark_convert: ColorShifting;
|
||||
}
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly q_scheme_color_expanding_methods: () => [number, number, number];
|
||||
readonly q_scheme_wacg_settings: () => [number, number, number];
|
||||
readonly differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly relative_differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly relative_differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly relative_differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly relative_differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly tint_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly shade_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly shift_hue: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly analogous_30: (a: number, b: number) => [number, number, number, number];
|
||||
readonly analogous_60: (a: number, b: number) => [number, number, number, number];
|
||||
readonly complementary: (a: number, b: number) => [number, number, number, number];
|
||||
readonly split_complementary: (a: number, b: number) => [number, number, number, number];
|
||||
readonly tetradic: (a: number, b: number) => [number, number, number, number];
|
||||
readonly triadic: (a: number, b: number) => [number, number, number, number];
|
||||
readonly __wbg_oklchdifference_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_oklchdifference_hue: (a: number) => number;
|
||||
readonly __wbg_set_oklchdifference_hue: (a: number, b: number) => void;
|
||||
readonly __wbg_get_oklchdifference_chroma: (a: number) => number;
|
||||
readonly __wbg_set_oklchdifference_chroma: (a: number, b: number) => void;
|
||||
readonly __wbg_get_oklchdifference_lightness: (a: number) => number;
|
||||
readonly __wbg_set_oklchdifference_lightness: (a: number, b: number) => void;
|
||||
readonly __wbg_differ_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_differ_delta: (a: number) => number;
|
||||
readonly __wbg_set_differ_delta: (a: number, b: number) => void;
|
||||
readonly __wbg_get_differ_percent: (a: number) => number;
|
||||
readonly __wbg_set_differ_percent: (a: number, b: number) => void;
|
||||
readonly generate_palette_from_color: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
||||
readonly __wbg_hctdiffference_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_hctdiffference_hue: (a: number) => number;
|
||||
readonly __wbg_set_hctdiffference_hue: (a: number, b: number) => void;
|
||||
readonly __wbg_get_hctdiffference_chroma: (a: number) => number;
|
||||
readonly __wbg_set_hctdiffference_chroma: (a: number, b: number) => void;
|
||||
readonly __wbg_get_hctdiffference_lightness: (a: number) => number;
|
||||
readonly __wbg_set_hctdiffference_lightness: (a: number, b: number) => void;
|
||||
readonly __wbg_mixreversing_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_mixreversing_r_factor: (a: number) => number;
|
||||
readonly __wbg_set_mixreversing_r_factor: (a: number, b: number) => void;
|
||||
readonly __wbg_get_mixreversing_g_factor: (a: number) => number;
|
||||
readonly __wbg_set_mixreversing_g_factor: (a: number, b: number) => void;
|
||||
readonly __wbg_get_mixreversing_b_factor: (a: number) => number;
|
||||
readonly __wbg_set_mixreversing_b_factor: (a: number, b: number) => void;
|
||||
readonly __wbg_get_mixreversing_average: (a: number) => number;
|
||||
readonly __wbg_set_mixreversing_average: (a: number, b: number) => void;
|
||||
readonly __wbg_rgbdifference_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_rgbdifference_r: (a: number) => number;
|
||||
readonly __wbg_set_rgbdifference_r: (a: number, b: number) => void;
|
||||
readonly __wbg_get_rgbdifference_g: (a: number) => number;
|
||||
readonly __wbg_set_rgbdifference_g: (a: number, b: number) => void;
|
||||
readonly __wbg_get_rgbdifference_b: (a: number) => number;
|
||||
readonly __wbg_set_rgbdifference_b: (a: number, b: number) => void;
|
||||
readonly __wbg_swatchentry_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_swatchentry_name: (a: number) => [number, number];
|
||||
readonly __wbg_set_swatchentry_name: (a: number, b: number, c: number) => void;
|
||||
readonly __wbg_get_swatchentry_color: (a: number) => [number, number];
|
||||
readonly __wbg_set_swatchentry_color: (a: number, b: number, c: number) => void;
|
||||
readonly color_categories: () => [number, number, number];
|
||||
readonly search_color_cards: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly __wbg_colorshifting_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_colorshifting_chroma: (a: number) => number;
|
||||
readonly __wbg_set_colorshifting_chroma: (a: number, b: number) => void;
|
||||
readonly __wbg_get_colorshifting_lightness: (a: number) => number;
|
||||
readonly __wbg_set_colorshifting_lightness: (a: number, b: number) => void;
|
||||
readonly __wbg_schemesetting_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_hover: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_hover: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_active: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_active: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_focus: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_focus: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_disabled: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_disabled: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_dark_convert: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_dark_convert: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_expand_method: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_expand_method: (a: number, b: number) => void;
|
||||
readonly __wbg_get_schemesetting_wacg_follows: (a: number) => number;
|
||||
readonly __wbg_set_schemesetting_wacg_follows: (a: number, b: number) => void;
|
||||
readonly generate_material_design_3_scheme: (a: number, b: number, c: number, d: number, e: any) => [number, number, number];
|
||||
readonly generate_material_design_2_scheme: (a: number, b: number, c: number, d: number, e: number, f: number, g: any) => [number, number, number];
|
||||
readonly generate_q_scheme_automatically: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number) => [number, number, number];
|
||||
readonly generate_q_scheme_manually: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number) => [number, number, number];
|
||||
readonly generate_swatch_scheme: (a: number, b: number, c: number) => [number, number, number];
|
||||
readonly lighten: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly lighten_absolute: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly darken: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly darken_absolute: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly mix: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
||||
readonly tint: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly shade: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly series: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
readonly tonal_lighten_series: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
readonly tonal_darken_series: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
readonly represent_rgb: (a: number, b: number) => [number, number, number, number];
|
||||
readonly rgb_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly represent_hsl: (a: number, b: number) => [number, number, number, number];
|
||||
readonly hsl_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly represent_lab: (a: number, b: number) => [number, number, number, number];
|
||||
readonly lab_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly represent_oklch: (a: number, b: number) => [number, number, number, number];
|
||||
readonly oklch_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly represent_hct: (a: number, b: number) => [number, number, number, number];
|
||||
readonly hct_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
readonly wacg_relative_contrast: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
readonly __wbg_swatchschemesetting_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_swatchschemesetting_amount: (a: number) => number;
|
||||
readonly __wbg_set_swatchschemesetting_amount: (a: number, b: number) => void;
|
||||
readonly __wbg_get_swatchschemesetting_min_lightness: (a: number) => number;
|
||||
readonly __wbg_set_swatchschemesetting_min_lightness: (a: number, b: number) => void;
|
||||
readonly __wbg_get_swatchschemesetting_max_lightness: (a: number) => number;
|
||||
readonly __wbg_set_swatchschemesetting_max_lightness: (a: number, b: number) => void;
|
||||
readonly __wbg_get_swatchschemesetting_include_primary: (a: number) => number;
|
||||
readonly __wbg_set_swatchschemesetting_include_primary: (a: number, b: number) => void;
|
||||
readonly __wbg_get_swatchschemesetting_dark_convert: (a: number) => number;
|
||||
readonly __wbg_set_swatchschemesetting_dark_convert: (a: number, b: number) => void;
|
||||
readonly __wbg_hsldifference_free: (a: number, b: number) => void;
|
||||
readonly __wbg_get_hsldifference_hue: (a: number) => number;
|
||||
readonly __wbg_set_hsldifference_hue: (a: number, b: number) => void;
|
||||
readonly __wbg_get_hsldifference_saturation: (a: number) => number;
|
||||
readonly __wbg_set_hsldifference_saturation: (a: number, b: number) => void;
|
||||
readonly __wbg_get_hsldifference_lightness: (a: number) => number;
|
||||
readonly __wbg_set_hsldifference_lightness: (a: number, b: number) => void;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly __externref_table_alloc: () => number;
|
||||
readonly __wbindgen_export_4: WebAssembly.Table;
|
||||
readonly __externref_table_dealloc: (a: number) => void;
|
||||
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
readonly __externref_drop_slice: (a: number, b: number) => void;
|
||||
readonly __wbindgen_start: () => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
138
src/color_functions/color_module_bg.wasm.d.ts
vendored
138
src/color_functions/color_module_bg.wasm.d.ts
vendored
@@ -1,138 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const q_scheme_color_expanding_methods: () => [number, number, number];
|
||||
export const q_scheme_wacg_settings: () => [number, number, number];
|
||||
export const differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const relative_differ_in_rgb: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const relative_differ_in_hsl: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const relative_differ_in_hct: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const relative_differ_in_oklch: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const tint_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const shade_scale: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const shift_hue: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const analogous_30: (a: number, b: number) => [number, number, number, number];
|
||||
export const analogous_60: (a: number, b: number) => [number, number, number, number];
|
||||
export const complementary: (a: number, b: number) => [number, number, number, number];
|
||||
export const split_complementary: (a: number, b: number) => [number, number, number, number];
|
||||
export const tetradic: (a: number, b: number) => [number, number, number, number];
|
||||
export const triadic: (a: number, b: number) => [number, number, number, number];
|
||||
export const __wbg_oklchdifference_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_oklchdifference_hue: (a: number) => number;
|
||||
export const __wbg_set_oklchdifference_hue: (a: number, b: number) => void;
|
||||
export const __wbg_get_oklchdifference_chroma: (a: number) => number;
|
||||
export const __wbg_set_oklchdifference_chroma: (a: number, b: number) => void;
|
||||
export const __wbg_get_oklchdifference_lightness: (a: number) => number;
|
||||
export const __wbg_set_oklchdifference_lightness: (a: number, b: number) => void;
|
||||
export const __wbg_differ_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_differ_delta: (a: number) => number;
|
||||
export const __wbg_set_differ_delta: (a: number, b: number) => void;
|
||||
export const __wbg_get_differ_percent: (a: number) => number;
|
||||
export const __wbg_set_differ_percent: (a: number, b: number) => void;
|
||||
export const generate_palette_from_color: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => [number, number, number, number];
|
||||
export const __wbg_hctdiffference_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_hctdiffference_hue: (a: number) => number;
|
||||
export const __wbg_set_hctdiffference_hue: (a: number, b: number) => void;
|
||||
export const __wbg_get_hctdiffference_chroma: (a: number) => number;
|
||||
export const __wbg_set_hctdiffference_chroma: (a: number, b: number) => void;
|
||||
export const __wbg_get_hctdiffference_lightness: (a: number) => number;
|
||||
export const __wbg_set_hctdiffference_lightness: (a: number, b: number) => void;
|
||||
export const __wbg_mixreversing_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_mixreversing_r_factor: (a: number) => number;
|
||||
export const __wbg_set_mixreversing_r_factor: (a: number, b: number) => void;
|
||||
export const __wbg_get_mixreversing_g_factor: (a: number) => number;
|
||||
export const __wbg_set_mixreversing_g_factor: (a: number, b: number) => void;
|
||||
export const __wbg_get_mixreversing_b_factor: (a: number) => number;
|
||||
export const __wbg_set_mixreversing_b_factor: (a: number, b: number) => void;
|
||||
export const __wbg_get_mixreversing_average: (a: number) => number;
|
||||
export const __wbg_set_mixreversing_average: (a: number, b: number) => void;
|
||||
export const __wbg_rgbdifference_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_rgbdifference_r: (a: number) => number;
|
||||
export const __wbg_set_rgbdifference_r: (a: number, b: number) => void;
|
||||
export const __wbg_get_rgbdifference_g: (a: number) => number;
|
||||
export const __wbg_set_rgbdifference_g: (a: number, b: number) => void;
|
||||
export const __wbg_get_rgbdifference_b: (a: number) => number;
|
||||
export const __wbg_set_rgbdifference_b: (a: number, b: number) => void;
|
||||
export const __wbg_swatchentry_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_swatchentry_name: (a: number) => [number, number];
|
||||
export const __wbg_set_swatchentry_name: (a: number, b: number, c: number) => void;
|
||||
export const __wbg_get_swatchentry_color: (a: number) => [number, number];
|
||||
export const __wbg_set_swatchentry_color: (a: number, b: number, c: number) => void;
|
||||
export const color_categories: () => [number, number, number];
|
||||
export const search_color_cards: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const __wbg_colorshifting_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_colorshifting_chroma: (a: number) => number;
|
||||
export const __wbg_set_colorshifting_chroma: (a: number, b: number) => void;
|
||||
export const __wbg_get_colorshifting_lightness: (a: number) => number;
|
||||
export const __wbg_set_colorshifting_lightness: (a: number, b: number) => void;
|
||||
export const __wbg_schemesetting_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_hover: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_hover: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_active: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_active: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_focus: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_focus: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_disabled: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_disabled: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_dark_convert: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_dark_convert: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_expand_method: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_expand_method: (a: number, b: number) => void;
|
||||
export const __wbg_get_schemesetting_wacg_follows: (a: number) => number;
|
||||
export const __wbg_set_schemesetting_wacg_follows: (a: number, b: number) => void;
|
||||
export const generate_material_design_3_scheme: (a: number, b: number, c: number, d: number, e: any) => [number, number, number];
|
||||
export const generate_material_design_2_scheme: (a: number, b: number, c: number, d: number, e: number, f: number, g: any) => [number, number, number];
|
||||
export const generate_q_scheme_automatically: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number) => [number, number, number];
|
||||
export const generate_q_scheme_manually: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number, r: number, s: number, t: number, u: number) => [number, number, number];
|
||||
export const generate_swatch_scheme: (a: number, b: number, c: number) => [number, number, number];
|
||||
export const lighten: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const lighten_absolute: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const darken: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const darken_absolute: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const mix: (a: number, b: number, c: number, d: number, e: number) => [number, number, number, number];
|
||||
export const tint: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const shade: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const series: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
export const tonal_lighten_series: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
export const tonal_darken_series: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
||||
export const represent_rgb: (a: number, b: number) => [number, number, number, number];
|
||||
export const rgb_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const represent_hsl: (a: number, b: number) => [number, number, number, number];
|
||||
export const hsl_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const represent_lab: (a: number, b: number) => [number, number, number, number];
|
||||
export const lab_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const represent_oklch: (a: number, b: number) => [number, number, number, number];
|
||||
export const oklch_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const represent_hct: (a: number, b: number) => [number, number, number, number];
|
||||
export const hct_to_hex: (a: number, b: number, c: number) => [number, number, number, number];
|
||||
export const wacg_relative_contrast: (a: number, b: number, c: number, d: number) => [number, number, number];
|
||||
export const __wbg_swatchschemesetting_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_swatchschemesetting_amount: (a: number) => number;
|
||||
export const __wbg_set_swatchschemesetting_amount: (a: number, b: number) => void;
|
||||
export const __wbg_get_swatchschemesetting_min_lightness: (a: number) => number;
|
||||
export const __wbg_set_swatchschemesetting_min_lightness: (a: number, b: number) => void;
|
||||
export const __wbg_get_swatchschemesetting_max_lightness: (a: number) => number;
|
||||
export const __wbg_set_swatchschemesetting_max_lightness: (a: number, b: number) => void;
|
||||
export const __wbg_get_swatchschemesetting_include_primary: (a: number) => number;
|
||||
export const __wbg_set_swatchschemesetting_include_primary: (a: number, b: number) => void;
|
||||
export const __wbg_get_swatchschemesetting_dark_convert: (a: number) => number;
|
||||
export const __wbg_set_swatchschemesetting_dark_convert: (a: number, b: number) => void;
|
||||
export const __wbg_hsldifference_free: (a: number, b: number) => void;
|
||||
export const __wbg_get_hsldifference_hue: (a: number) => number;
|
||||
export const __wbg_set_hsldifference_hue: (a: number, b: number) => void;
|
||||
export const __wbg_get_hsldifference_saturation: (a: number) => number;
|
||||
export const __wbg_set_hsldifference_saturation: (a: number, b: number) => void;
|
||||
export const __wbg_get_hsldifference_lightness: (a: number) => number;
|
||||
export const __wbg_set_hsldifference_lightness: (a: number, b: number) => void;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_exn_store: (a: number) => void;
|
||||
export const __externref_table_alloc: () => number;
|
||||
export const __wbindgen_export_4: WebAssembly.Table;
|
||||
export const __externref_table_dealloc: (a: number) => void;
|
||||
export const __wbindgen_free: (a: number, b: number, c: number) => void;
|
||||
export const __externref_drop_slice: (a: number, b: number) => void;
|
||||
export const __wbindgen_start: () => void;
|
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"name": "color-module",
|
||||
"type": "module",
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"color_module_bg.wasm",
|
||||
"color_module.js",
|
||||
"color_module.d.ts"
|
||||
],
|
||||
"main": "color_module.js",
|
||||
"types": "color_module.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
@@ -193,6 +193,7 @@
|
||||
resize: none;
|
||||
}
|
||||
.input_wrapper {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@@ -440,4 +441,12 @@
|
||||
background-color: var(--color-info);
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-xxs);
|
||||
font-size: var(--font-size-xs);
|
||||
line-height: 1.4em;
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import { Icon, IconProps } from '@iconify/react/dist/iconify.js';
|
||||
import cx from 'clsx';
|
||||
import { MouseEventHandler, useCallback } from 'react';
|
||||
import { MouseEvent, MouseEventHandler, RefObject, useCallback } from 'react';
|
||||
import styles from './ActionIcon.module.css';
|
||||
|
||||
type ActionIconProps = {
|
||||
icon: IconProps['icon'];
|
||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||
extendClassName?: HTMLButtonElement['className'];
|
||||
ref?: RefObject<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
export function ActionIcon({ icon, onClick, extendClassName }: ActionIconProps) {
|
||||
export function ActionIcon({ icon, onClick, extendClassName, ref }: ActionIconProps) {
|
||||
const handleClick = useCallback(
|
||||
(event: MouseEvent<HTMLButtonElement>) => {
|
||||
onClick?.(event);
|
||||
@@ -18,7 +19,11 @@ export function ActionIcon({ icon, onClick, extendClassName }: ActionIconProps)
|
||||
);
|
||||
|
||||
return (
|
||||
<button onClick={handleClick} className={cx(styles.action_icon, extendClassName)}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClick}
|
||||
className={cx(styles.action_icon, extendClassName)}
|
||||
ref={ref}>
|
||||
<Icon icon={icon} className={styles.icon} />
|
||||
</button>
|
||||
);
|
||||
|
@@ -3,7 +3,10 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-m);
|
||||
gap: var(--spacing-xs);
|
||||
.extended_input_wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
.rgb_input {
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
|
@@ -87,7 +87,7 @@ export function ColorComponentInput({ color, onChange }: ColorComponentInputProp
|
||||
}
|
||||
};
|
||||
const updateH = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseInt(evt.target.value, 10);
|
||||
let value = parseInt(evt.target.value, 10);
|
||||
if (value > 360) {
|
||||
value -= 360;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ export function ColorComponentInput({ color, onChange }: ColorComponentInputProp
|
||||
|
||||
return (
|
||||
<div className={styles.rgb_input}>
|
||||
<div className={cx('input_wrapper')}>
|
||||
<div className={cx('input_wrapper', styles.extended_input_wrapper)}>
|
||||
<Icon icon="tabler:hash" />
|
||||
<input type="text" value={hex} onChange={updateHex} className={styles.rgb_input} />
|
||||
</div>
|
||||
|
@@ -28,7 +28,7 @@ export function ColorRangePicker({
|
||||
}: ColorRangePickerProps) {
|
||||
const [pickerValue, setPickerValue] = useState(value);
|
||||
const handlePickerChange = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = evt.target.value as number;
|
||||
const value = Number(evt.target.value);
|
||||
setPickerValue(valueProcess(value));
|
||||
onChange?.(valueProcess(value));
|
||||
};
|
||||
|
42
src/components/ContextMenu.module.css
Normal file
42
src/components/ContextMenu.module.css
Normal file
@@ -0,0 +1,42 @@
|
||||
@layer components {
|
||||
.context_menu_locationer {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
.action_icon {
|
||||
background-color: transparent;
|
||||
&:hover {
|
||||
background-color: var(--color-neutral-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-neutral-active);
|
||||
}
|
||||
}
|
||||
.menu_body {
|
||||
position: absolute;
|
||||
width: max-content;
|
||||
background-color: var(--color-wumeizi);
|
||||
color: var(--color-yudubai);
|
||||
border: 1px solid var(--color-xuanqing);
|
||||
border-radius: var(--border-radius-xs);
|
||||
padding-block: var(--spacing-xs);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 300;
|
||||
.menu_item {
|
||||
width: 100%;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
&:hover {
|
||||
background-color: var(--color-primary-hover);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--color-primary-active);
|
||||
}
|
||||
}
|
||||
hr {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
466
src/components/ContextMenu.tsx
Normal file
466
src/components/ContextMenu.tsx
Normal file
@@ -0,0 +1,466 @@
|
||||
import { SwatchEntry } from 'color-module';
|
||||
import { useAtomValue, useSetAtom } from 'jotai';
|
||||
import { capitalize, size } from 'lodash-es';
|
||||
import {
|
||||
FC,
|
||||
MouseEvent,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { MaterialDesign2SchemeSource } from '../material-2-scheme';
|
||||
import {
|
||||
MaterialDesign3DynamicSchemeSource,
|
||||
MaterialDesign3SchemeSource,
|
||||
} from '../material-3-scheme';
|
||||
import { Q2SchemeSource } from '../q-2-scheme';
|
||||
import { QSchemeSource } from '../q-scheme';
|
||||
import { currentPickedColor } from '../stores/colors';
|
||||
import { activeSchemeAtom, useActiveScheme, useUpdateScheme } from '../stores/schemes';
|
||||
import { SwatchSchemeSource } from '../swatch_scheme';
|
||||
import { ActionIcon } from './ActionIcon';
|
||||
import styles from './ContextMenu.module.css';
|
||||
import { NotificationType, useNotification } from './Notifications';
|
||||
|
||||
interface ContextMenuItemProps {
|
||||
color: string;
|
||||
afterClick?: () => void;
|
||||
}
|
||||
|
||||
const SetPickerMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const setCurrentPicker = useSetAtom(currentPickedColor);
|
||||
const handleClickAction = useCallback(() => {
|
||||
setCurrentPicker(color);
|
||||
afterClick?.();
|
||||
}, [afterClick, color]);
|
||||
|
||||
return (
|
||||
<div className={styles.menu_item} onClick={handleClickAction}>
|
||||
Set to default Picker
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const QSchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeContent = useCallback(
|
||||
(content: keyof QSchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('primary')}>
|
||||
Set as Primary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('secondary')}>
|
||||
Set as Secondary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('tertiary')}>
|
||||
Set as Tertiary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('accent')}>
|
||||
Set as Accent color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('danger')}>
|
||||
Set as Danger color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('success')}>
|
||||
Set as Success color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('warning')}>
|
||||
Set as Warn color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('info')}>
|
||||
Set as Info color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('foreground')}>
|
||||
Set as Foreground color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('background')}>
|
||||
Set as Background color
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Q2SchemeMenu: FC<ContextMenuBodyProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeContent = useCallback(
|
||||
(content: keyof Q2SchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
const addCustomColor = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as Q2SchemeSource;
|
||||
const colorAmount = size(source.custom_colors);
|
||||
source.custom_colors[`Custom Color ${colorAmount + 1}`] = color;
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(NotificationType.SUCCESS, `New color entry added.`, 'tabler:settings-up', 3000);
|
||||
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('primary')}>
|
||||
Set as Primary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('secondary')}>
|
||||
Set as Secondary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('tertiary')}>
|
||||
Set as Tertiary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('accent')}>
|
||||
Set as Accent color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('danger')}>
|
||||
Set as Danger color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('success')}>
|
||||
Set as Success color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('warn')}>
|
||||
Set as Warn color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('info')}>
|
||||
Set as Info color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('foreground')}>
|
||||
Set as Foreground color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('background')}>
|
||||
Set as Background color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={addCustomColor}>
|
||||
Add to Custom colors
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SwatchSchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const addColorEntry = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as SwatchSchemeSource;
|
||||
|
||||
const colorAmount = source.colors.length;
|
||||
const newEntry = new SwatchEntry(`Custom Color ${colorAmount + 1}`, color);
|
||||
source.colors.push(newEntry.toJsValue());
|
||||
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(NotificationType.SUCCESS, 'New color entry added.', 'tabler:settings-up', 3000);
|
||||
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={addColorEntry}>
|
||||
Add to swatch color
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Material2SchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeColor = useCallback(
|
||||
(content: keyof MaterialDesign2SchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
const addToCustomColor = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as MaterialDesign2SchemeSource;
|
||||
const colorAmount = size(source.custom_colors);
|
||||
source.custom_colors[`Custom Color ${colorAmount + 1}`] = color;
|
||||
return prev;
|
||||
});
|
||||
showToast(NotificationType.SUCCESS, `New color entry added.`, 'tabler:settings-up', 3000);
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeColor('primary')}>
|
||||
Set as Primary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeColor('secondary')}>
|
||||
Set as Secondary color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeColor('error')}>
|
||||
Set as Error color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => addToCustomColor()}>
|
||||
Add to Custom colors
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Material3SchemeMenu: FC<ContextMenuItemProps> = ({ color, afterClick }) => {
|
||||
const { showToast } = useNotification();
|
||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||
const updateScheme = useUpdateScheme(activeSchemeId);
|
||||
const updateSchemeContent = useCallback(
|
||||
(content: keyof MaterialDesign3SchemeSource) => {
|
||||
updateScheme((prev) => {
|
||||
prev.schemeStorage.source[content] = color;
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`${capitalize(content)} color in active scheme updated.`,
|
||||
'tabler:settings-up',
|
||||
3000,
|
||||
);
|
||||
|
||||
afterClick?.();
|
||||
},
|
||||
[color, activeSchemeId, updateScheme],
|
||||
);
|
||||
const addCustomColor = useCallback(() => {
|
||||
updateScheme((prev) => {
|
||||
const source = prev.schemeStorage.source as
|
||||
| MaterialDesign3DynamicSchemeSource
|
||||
| MaterialDesign3SchemeSource;
|
||||
const colorAmount = size(source.custom_colors);
|
||||
source.custom_colors[`Custom Color ${colorAmount + 1}`] = color;
|
||||
return prev;
|
||||
});
|
||||
|
||||
showToast(NotificationType.SUCCESS, `New color entry added.`, 'tabler:settings-up', 3000);
|
||||
|
||||
afterClick?.();
|
||||
}, [color, activeSchemeId, updateScheme]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('source')}>
|
||||
Set as Source color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={() => updateSchemeContent('error')}>
|
||||
Set as Error color
|
||||
</div>
|
||||
<div className={styles.menu_item} onClick={addCustomColor}>
|
||||
Add to Custom colors
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface ContextMenuBodyProps {
|
||||
color: string;
|
||||
afterClick?: () => void;
|
||||
x?: number;
|
||||
y?: number;
|
||||
ref?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const ContextMenuBody: FC<ContextMenuBodyProps> = ({ color, afterClick, x, y, ref }) => {
|
||||
const activeScheme = useActiveScheme();
|
||||
|
||||
const schemeMenu = useMemo(() => {
|
||||
const sharedProps: ContextMenuItemProps = {
|
||||
color,
|
||||
afterClick,
|
||||
};
|
||||
switch (activeScheme?.type) {
|
||||
case 'q_scheme':
|
||||
return <QSchemeMenu {...sharedProps} />;
|
||||
case 'q_2_scheme':
|
||||
return <Q2SchemeMenu {...sharedProps} />;
|
||||
case 'swatch_scheme':
|
||||
return <SwatchSchemeMenu {...sharedProps} />;
|
||||
case 'material_2':
|
||||
return <Material2SchemeMenu {...sharedProps} />;
|
||||
case 'material_3':
|
||||
case 'material_3_dynamic':
|
||||
return <Material3SchemeMenu {...sharedProps} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, [activeScheme, color, afterClick]);
|
||||
|
||||
return (
|
||||
<div className={styles.menu_body} ref={ref} style={{ top: y, left: x }}>
|
||||
<SetPickerMenu color={color} afterClick={afterClick} />
|
||||
{schemeMenu}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface ContextMenuProps {
|
||||
color: string;
|
||||
}
|
||||
|
||||
const ContextMenu: FC<ContextMenuProps> = ({ color }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [initialPosition, setInitialPosition] = useState({ x: 0, y: 0 });
|
||||
const [renderPosition, setRenderPosition] = useState({ x: 0, y: 0 });
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleOpenMenu = useCallback(() => {
|
||||
if (isOpen) {
|
||||
setIsOpen(false);
|
||||
return;
|
||||
}
|
||||
if (triggerRef.current && containerRef.current) {
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
|
||||
const x = triggerRect.left - containerRect.left;
|
||||
const y = triggerRect.bottom - containerRect.top;
|
||||
|
||||
setInitialPosition({ x, y });
|
||||
setRenderPosition({ x, y });
|
||||
setIsOpen(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
const handleCloseMenu = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
}, []);
|
||||
const handleLeaveClose = useCallback(
|
||||
(evt: MouseEvent<HTMLDivElement>) => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
const relatedTarget = evt.relatedTarget as Node | null;
|
||||
|
||||
if (menuRef.current && menuRef.current.contains(relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (triggerRef.current && triggerRef.current.contains(relatedTarget)) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleCloseMenu();
|
||||
},
|
||||
[handleCloseMenu, isOpen],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && menuRef.current && containerRef.current && triggerRef.current) {
|
||||
const menuElemenet = menuRef.current;
|
||||
const triggerRect = triggerRef.current.getBoundingClientRect();
|
||||
const containerRect = containerRef.current.getBoundingClientRect();
|
||||
|
||||
const menuHeight = menuElemenet.offsetHeight;
|
||||
const menuWidth = menuElemenet.offsetWidth;
|
||||
|
||||
const viewportHeight = window.innerHeight;
|
||||
const viewportWidth = window.innerWidth;
|
||||
|
||||
const viewportX = containerRect.left + initialPosition.x;
|
||||
const viewportY = containerRect.top + initialPosition.y;
|
||||
|
||||
let adjustedX = initialPosition.x;
|
||||
let adjustedY = initialPosition.y;
|
||||
|
||||
if (viewportX + menuWidth > viewportWidth) {
|
||||
adjustedX = initialPosition.x - menuWidth + triggerRect.width;
|
||||
if (containerRect.left + adjustedX < 0) {
|
||||
adjustedX = -containerRect.left + 5; // 留5px边距
|
||||
}
|
||||
}
|
||||
|
||||
if (viewportY + menuHeight > viewportHeight) {
|
||||
adjustedY = initialPosition.y - menuHeight - triggerRect.height;
|
||||
if (containerRect.top + adjustedY < 0) {
|
||||
adjustedY = -containerRect.top + 5; // 留5px边距
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustedX !== renderPosition.x || adjustedY !== renderPosition.y) {
|
||||
setRenderPosition({ x: adjustedX, y: adjustedY });
|
||||
}
|
||||
}
|
||||
}, [isOpen, initialPosition, renderPosition.x, renderPosition.y]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.context_menu_locationer}
|
||||
ref={containerRef}
|
||||
onMouseLeave={handleLeaveClose}>
|
||||
<ActionIcon
|
||||
icon="tabler:dots-vertical"
|
||||
extendClassName={styles.action_icon}
|
||||
onClick={handleOpenMenu}
|
||||
ref={triggerRef}
|
||||
/>
|
||||
{isOpen && (
|
||||
<ContextMenuBody
|
||||
color={color}
|
||||
afterClick={handleCloseMenu}
|
||||
x={renderPosition.x}
|
||||
y={renderPosition.y}
|
||||
ref={menuRef}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
@@ -4,7 +4,7 @@ import { useRef, useState } from 'react';
|
||||
import styles from './EditableTitle.module.css';
|
||||
|
||||
type EditableTitleProps = {
|
||||
title: string;
|
||||
title?: string;
|
||||
onChange?: (newTitle: string) => void;
|
||||
};
|
||||
|
||||
|
@@ -11,12 +11,19 @@
|
||||
.color_block {
|
||||
flex: 1 0;
|
||||
}
|
||||
.color_value {
|
||||
.operate_row {
|
||||
padding: var(--spacing-xxs) var(--spacing-xs);
|
||||
font-size: var(--font-size-xs);
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-s);
|
||||
.color_value {
|
||||
text-align: right;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useColorFunction } from '../ColorFunctionContext';
|
||||
import { useCopyColor } from '../hooks/useCopyColor';
|
||||
import ContextMenu from './ContextMenu';
|
||||
import styles from './FlexColorStand.module.css';
|
||||
|
||||
type FlexColorStandProps = {
|
||||
@@ -51,8 +52,11 @@ export function FlexColorStand({ color, valueMode = 'hex' }: FlexColorStandProps
|
||||
return (
|
||||
<div className={styles.color_stand}>
|
||||
<div className={styles.color_block} style={{ backgroundColor: bgColor }} />
|
||||
<div className={styles.color_value} onClick={() => copyToClipboard(colorValue)}>
|
||||
{bgColor}
|
||||
<div className={styles.operate_row}>
|
||||
<div className={styles.color_value} onClick={() => copyToClipboard(colorValue)}>
|
||||
{bgColor}
|
||||
</div>
|
||||
<ContextMenu color={color} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
44
src/components/FloatColorPicker.module.css
Normal file
44
src/components/FloatColorPicker.module.css
Normal file
@@ -0,0 +1,44 @@
|
||||
@layer components {
|
||||
.float_color_picker {
|
||||
position: relative;
|
||||
height: 22px;
|
||||
.preview {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
z-index: 25;
|
||||
.preview_block {
|
||||
height: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: var(--border-radius-xxs);
|
||||
border: 1px solid var(--color-border);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
}
|
||||
.picker {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
padding: var(--spacing-s) var(--spacing-s);
|
||||
border-radius: var(--border-radius-xxs);
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-bg);
|
||||
box-shadow: 2px 0 8px oklch(from var(--color-black) l c h / 65%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-s);
|
||||
z-index: 260;
|
||||
.btns {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
56
src/components/FloatColorPicker.tsx
Normal file
56
src/components/FloatColorPicker.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { ActionIcon } from './ActionIcon';
|
||||
import { ColorPicker } from './ColorPicker';
|
||||
import styles from './FloatColorPicker.module.css';
|
||||
|
||||
type FloatColorPickerProps = {
|
||||
name?: string;
|
||||
color?: string | null;
|
||||
onPick?: (color: string | null | undefined) => void;
|
||||
};
|
||||
|
||||
export function FloatColorPicker({ name, color, onPick }: FloatColorPickerProps) {
|
||||
const [pickedColor, setPicked] = useState<string | null>(color ?? null);
|
||||
const [showPicker, setPickerShow] = useState(false);
|
||||
const handlePickAction = useCallback(
|
||||
(value: string) => {
|
||||
setPicked(value);
|
||||
onPick?.(value);
|
||||
},
|
||||
[onPick],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEqual(pickedColor, color)) {
|
||||
setPicked(color);
|
||||
}
|
||||
}, [color]);
|
||||
|
||||
return (
|
||||
<div className={styles.float_color_picker}>
|
||||
<div className={styles.preview}>
|
||||
<div
|
||||
className={styles.preview_block}
|
||||
onClick={() => setPickerShow(true)}
|
||||
style={{
|
||||
backgroundColor: isNil(pickedColor) ? 'rgba(0, 0, 0, 0)' : `#${pickedColor}`,
|
||||
}}>
|
||||
{isNil(pickedColor) && <span>N/A</span>}
|
||||
</div>
|
||||
<ActionIcon icon="tabler:x" onClick={() => handlePickAction(null)} />
|
||||
</div>
|
||||
{showPicker && (
|
||||
<div className={styles.picker}>
|
||||
<ColorPicker color={pickedColor ?? null} onSelect={handlePickAction} />
|
||||
<div className={styles.btns}>
|
||||
<button type="button" className="primary" onClick={() => setPickerShow(false)}>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isNil(name) && <input type="hidden" name={name} value={pickedColor ?? ''} />}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,10 +1,12 @@
|
||||
import cx from 'clsx';
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { isEqual, isMap, isNil } from 'lodash-es';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { Option } from '../models';
|
||||
import styles from './HSegmentedControl.module.css';
|
||||
|
||||
type HSegmentedControlProps = {
|
||||
name?: string;
|
||||
defaultValue?: Option['value'];
|
||||
options?: Option[];
|
||||
value?: Option['value'];
|
||||
onChange?: (value: Option['value']) => void;
|
||||
@@ -12,12 +14,19 @@ type HSegmentedControlProps = {
|
||||
};
|
||||
|
||||
export function HSegmentedControl({
|
||||
name,
|
||||
defaultValue,
|
||||
options = [],
|
||||
value,
|
||||
onChange,
|
||||
extendClassName,
|
||||
}: HSegmentedControlProps) {
|
||||
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
|
||||
const [selected, setSelected] = useState(
|
||||
value ??
|
||||
defaultValue ??
|
||||
(isMap(options[0]) ? options[0].get('value') : options[0].value) ??
|
||||
null,
|
||||
);
|
||||
const [sliderPosition, setSliderPosition] = useState(0);
|
||||
const [sliderWidth, setSliderWidth] = useState(0);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
@@ -36,15 +45,20 @@ export function HSegmentedControl({
|
||||
return (
|
||||
<div className={cx(styles.segmented_control, extendClassName)}>
|
||||
<div className={styles.options}>
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={`${index}_${option.value}`}
|
||||
className={cx(styles.option, isEqual(selected, option.value) && styles.selected)}
|
||||
ref={(el) => (optionsRef.current[index] = el!)}
|
||||
onClick={() => handleSelectAction(option.value, index)}>
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
{options.map((option, index) => {
|
||||
const label = isMap(option) ? option.get('label') : option.label;
|
||||
const value = isMap(option) ? option.get('value') : option.value;
|
||||
return (
|
||||
<div
|
||||
key={`${index}_${value}`}
|
||||
className={cx(styles.option, isEqual(selected, value) && styles.selected)}
|
||||
//@ts-expect-error TS2322
|
||||
ref={(el) => (optionsRef.current[index] = el!)}
|
||||
onClick={() => handleSelectAction(value, index)}>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!isNil(selected) && (
|
||||
<div
|
||||
className={styles.slider}
|
||||
@@ -53,6 +67,7 @@ export function HSegmentedControl({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isNil(name) && <input type="hidden" name={name} value={selected} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ export function LabeledPicker({
|
||||
}: LabeledPickerProps) {
|
||||
const [pickerValue, setPickerValue] = useState(value ?? min);
|
||||
const handlePickerChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = event.target.value as number;
|
||||
const value = Number(event.target.value);
|
||||
setPickerValue(value);
|
||||
onChange?.(value);
|
||||
};
|
||||
|
@@ -118,7 +118,7 @@ type ToastProps = {
|
||||
icon?: string;
|
||||
duration?: ToastDuration;
|
||||
ref: RefObject<HTMLDivElement>;
|
||||
closeAction: () => void;
|
||||
closeAction: (tid?: string) => void;
|
||||
};
|
||||
const Toast = ({
|
||||
kind,
|
||||
@@ -157,7 +157,7 @@ export function useNotification() {
|
||||
type NotificationElement = {
|
||||
id: string;
|
||||
element: ReactNode;
|
||||
ref: RefObject<ReactNode>;
|
||||
ref: RefObject<ReactNode | HTMLDivElement>;
|
||||
};
|
||||
type NotificationsProps = {
|
||||
defaultDuration?: number;
|
||||
@@ -184,7 +184,7 @@ export function Notifications({
|
||||
duration?: number,
|
||||
) => {
|
||||
const id = v4();
|
||||
const ref = createRef(null);
|
||||
const ref = createRef<ReactNode | HTMLDivElement>();
|
||||
const newNotify = (
|
||||
<Notification
|
||||
kind={kind}
|
||||
@@ -194,6 +194,7 @@ export function Notifications({
|
||||
message={message}
|
||||
duration={duration ?? defaultDuration}
|
||||
closeAction={removeNotification}
|
||||
//@ts-expect-error TS2322
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
@@ -207,14 +208,9 @@ export function Notifications({
|
||||
setToasts((prev) => filter(prev, (n) => !isEqual(n.id, id)));
|
||||
}, []);
|
||||
const showToast = useCallback(
|
||||
(
|
||||
kind: NotificationType,
|
||||
message?: string,
|
||||
icon?: IconifyIconProps['icon'],
|
||||
duration?: ToastDuration,
|
||||
) => {
|
||||
(kind: NotificationType, message?: string, icon?: string, duration?: ToastDuration) => {
|
||||
const id = v4();
|
||||
const ref = createRef(null);
|
||||
const ref = createRef<HTMLDivElement>();
|
||||
const newToast = (
|
||||
<Toast
|
||||
kind={kind}
|
||||
@@ -238,9 +234,6 @@ export function Notifications({
|
||||
value={{
|
||||
addNotification,
|
||||
removeNotification,
|
||||
showDialog: () => '',
|
||||
showModalDialog: () => '',
|
||||
closeDialog: () => {},
|
||||
showToast,
|
||||
}}>
|
||||
{children}
|
||||
@@ -250,6 +243,7 @@ export function Notifications({
|
||||
{notifications.slice(0, maxNotifications).map(({ id, element, ref }) => (
|
||||
<CSSTransition
|
||||
key={id}
|
||||
//@ts-expect-error TS2322
|
||||
nodeRef={ref}
|
||||
unmountOnExit
|
||||
timeout={500}
|
||||
@@ -271,6 +265,7 @@ export function Notifications({
|
||||
{toasts.slice(0, 1).map(({ id, element, ref }) => (
|
||||
<CSSTransition
|
||||
key={id}
|
||||
//@ts-expect-error TS2322
|
||||
nodeRef={ref}
|
||||
unmountOnExit
|
||||
timeout={500}
|
||||
|
@@ -9,6 +9,9 @@
|
||||
&.swatch {
|
||||
background-color: var(--color-pinlan);
|
||||
}
|
||||
&.q2 {
|
||||
background-color: var(--color-jugengzi);
|
||||
}
|
||||
&.m2 {
|
||||
background-color: #03dac6;
|
||||
color: var(--color-qihei);
|
||||
@@ -17,5 +20,9 @@
|
||||
background-color: #a78fff;
|
||||
color: var(--color-qihei);
|
||||
}
|
||||
&.m3d {
|
||||
background-color: #ffde3f;
|
||||
color: var(--color-qihei);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,12 +16,16 @@ export function SchemeSign({ scheme, short = false }: SchemeSignProps) {
|
||||
switch (scheme) {
|
||||
case 'q_scheme':
|
||||
return styles.q;
|
||||
case 'q_2_scheme':
|
||||
return styles.q2;
|
||||
case 'swatch_scheme':
|
||||
return styles.swatch;
|
||||
case 'material_2':
|
||||
return styles.m2;
|
||||
case 'material_3':
|
||||
return styles.m3;
|
||||
case 'material_3_dynamic':
|
||||
return styles.m3d;
|
||||
}
|
||||
}, [scheme]);
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { clamp } from 'lodash-es';
|
||||
import { RefObject, useEffect, useRef, useState } from 'react';
|
||||
import { MouseEvent, RefObject, useEffect, useRef, useState, WheelEvent } from 'react';
|
||||
import styles from './ScrollArea.module.css';
|
||||
|
||||
type ScrollBarProps = {
|
||||
@@ -12,10 +12,12 @@ function VerticalScrollBar({ containerRef }: ScrollBarProps) {
|
||||
const thumbRef = useRef<HTMLDivElement | null>(null);
|
||||
const handleMouseDown = (evt: MouseEvent) => {
|
||||
evt.preventDefault();
|
||||
//@ts-expect-error TS2769
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
//@ts-expect-error TS2769
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
const handleMouseMove = (evt: MouseEvent) => {
|
||||
const handleMouseMove = (evt: MouseEvent<HTMLDivElement>) => {
|
||||
evt.preventDefault();
|
||||
const container = containerRef?.current;
|
||||
const scrollbar = trackRef.current;
|
||||
@@ -34,7 +36,9 @@ function VerticalScrollBar({ containerRef }: ScrollBarProps) {
|
||||
};
|
||||
const handleMouseUp = (evt: MouseEvent) => {
|
||||
evt.preventDefault();
|
||||
//@ts-expect-error TS2769
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
//@ts-expect-error TS2769
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
@@ -77,7 +81,9 @@ function HorizontalScrollBar({ containerRef }: ScrollBarProps) {
|
||||
const thumbRef = useRef<HTMLDivElement | null>(null);
|
||||
const handleMouseDown = (evt: MouseEvent) => {
|
||||
evt.preventDefault();
|
||||
//@ts-expect-error TS2769
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
//@ts-expect-error TS2769
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
const handleMouseMove = (evt: MouseEvent) => {
|
||||
@@ -99,7 +105,9 @@ function HorizontalScrollBar({ containerRef }: ScrollBarProps) {
|
||||
};
|
||||
const handleMouseUp = (evt: MouseEvent) => {
|
||||
evt.preventDefault();
|
||||
//@ts-expect-error TS2769
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
//@ts-expect-error TS2769
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
@@ -129,7 +137,7 @@ function HorizontalScrollBar({ containerRef }: ScrollBarProps) {
|
||||
className={styles.h_thumb}
|
||||
ref={thumbRef}
|
||||
style={{ left: thumbPos }}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseDown={(e) => handleMouseDown(e)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -148,10 +156,10 @@ export function ScrollArea({
|
||||
enableY = false,
|
||||
normalizedScroll = false,
|
||||
}: ScrollAreaProps) {
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [xScrollNeeded, setXScrollNeeded] = useState(false);
|
||||
const [yScrollNeeded, setYScrollNeeded] = useState(false);
|
||||
const handleWheel = (evt: WheelEvent) => {
|
||||
const handleWheel = (evt: WheelEvent<HTMLDivElement>) => {
|
||||
const container = scrollContainerRef?.current;
|
||||
if (enableY && container) {
|
||||
const delta = evt.deltaY;
|
||||
@@ -177,7 +185,7 @@ export function ScrollArea({
|
||||
|
||||
return (
|
||||
<div className={styles.scroll_area}>
|
||||
<div className={styles.content} ref={scrollContainerRef} onWheel={handleWheel}>
|
||||
<div className={styles.content} ref={scrollContainerRef} onWheel={(e) => handleWheel(e)}>
|
||||
{children}
|
||||
</div>
|
||||
{enableY && yScrollNeeded && <VerticalScrollBar containerRef={scrollContainerRef} />}
|
||||
|
@@ -1,15 +1,16 @@
|
||||
import cx from 'clsx';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styles from './Switch.module.css';
|
||||
|
||||
type SwitchProps = {
|
||||
name?: string;
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
onChange?: (checked: boolean) => void;
|
||||
};
|
||||
|
||||
export function Switch({ checked = false, disabled = false, onChange }: SwitchProps) {
|
||||
export function Switch({ name, checked = false, disabled = false, onChange }: SwitchProps) {
|
||||
const [isChecked, setIsChecked] = useState(checked);
|
||||
const handleSwitch = useCallback(() => {
|
||||
if (!disabled) {
|
||||
@@ -25,10 +26,12 @@ export function Switch({ checked = false, disabled = false, onChange }: SwitchPr
|
||||
}, [checked]);
|
||||
|
||||
return (
|
||||
//@ts-expect-error TS2322
|
||||
<div className={styles.switch} disabled={disabled}>
|
||||
<div
|
||||
className={cx(styles.switch_handle, isChecked && styles.checked)}
|
||||
onClick={handleSwitch}></div>
|
||||
{!isNil(name) && <input type="hidden" name={name} value={isChecked ? 'true' : 'false'} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -18,5 +18,9 @@
|
||||
&:hover {
|
||||
color: var(--color-primary-hover);
|
||||
}
|
||||
&.disabled {
|
||||
color: var(--color-primary-disabled);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,50 @@
|
||||
import cx from 'clsx';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import styles from './Tab.module.css';
|
||||
|
||||
type TabProps = {
|
||||
tabs: { title: string; id: unknown }[];
|
||||
onActive?: (id: unknown) => void;
|
||||
type TabOption = {
|
||||
title: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function Tab({ tabs = [], onActive }: TabProps) {
|
||||
const [active, setActive] = useState(0);
|
||||
const handleActivate = useCallback((index: number) => {
|
||||
setActive(index);
|
||||
onActive?.(tabs[index].id);
|
||||
}, []);
|
||||
type TabProps = {
|
||||
tabs: TabOption[];
|
||||
activeTab?: unknown;
|
||||
onActive?: (id: TabOption['id']) => void;
|
||||
disabled?: Record<TabOption['id'], boolean>;
|
||||
};
|
||||
|
||||
export function Tab({ tabs = [], activeTab, onActive, disabled }: TabProps) {
|
||||
const [active, setActive] = useState(() =>
|
||||
isNil(activeTab) ? 0 : tabs.findIndex((tab) => isEqual(tab.id, activeTab)),
|
||||
);
|
||||
const handleActivate = useCallback(
|
||||
(index: number) => {
|
||||
if (disabled?.[tabs[index].id] ?? false) return;
|
||||
setActive(index);
|
||||
onActive?.(tabs[index].id);
|
||||
},
|
||||
[tabs, onActive, disabled],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const activeIndex = tabs.findIndex((tab) => isEqual(tab.id, activeTab));
|
||||
if (!isNil(activeIndex) && !isEqual(activeIndex, -1) && !isEqual(activeIndex, active)) {
|
||||
setActive(activeIndex);
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
return (
|
||||
<div className={styles.tabs_container}>
|
||||
{tabs.map((tab, index) => (
|
||||
<div
|
||||
key={`tab_${index}_${tab.id}`}
|
||||
className={cx(styles.tab, isEqual(index, active) && styles.actived)}
|
||||
className={cx(
|
||||
styles.tab,
|
||||
isEqual(index, active) && styles.actived,
|
||||
(disabled?.[tab.id] ?? false) && styles.disabled,
|
||||
)}
|
||||
onClick={() => handleActivate(index)}>
|
||||
{tab.title}
|
||||
</div>
|
||||
|
@@ -18,7 +18,7 @@ const positionMap = {
|
||||
|
||||
export function Tooltip({ content, position = 'top', children }: TooltipProps) {
|
||||
const [show, setShow] = useState(false);
|
||||
const contentRef = useRef<HTMLDivElement>();
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@@ -1,10 +1,12 @@
|
||||
import cx from 'clsx';
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { isEqual, isMap, isNil } from 'lodash-es';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import type { Option } from '../models';
|
||||
import styles from './VSegmentedControl.module.css';
|
||||
|
||||
type VSegmentedControlProps = {
|
||||
name?: string;
|
||||
defaultValue?: Option['value'];
|
||||
options?: Option[];
|
||||
value?: Option['value'];
|
||||
onChange?: (value: Option['value']) => void;
|
||||
@@ -12,12 +14,19 @@ type VSegmentedControlProps = {
|
||||
};
|
||||
|
||||
export function VSegmentedControl({
|
||||
name,
|
||||
defaultValue,
|
||||
options = [],
|
||||
value,
|
||||
onChange,
|
||||
extendClassName,
|
||||
}: VSegmentedControlProps) {
|
||||
const [selected, setSelected] = useState(value ?? options[0].value ?? null);
|
||||
const [selected, setSelected] = useState(
|
||||
value ??
|
||||
defaultValue ??
|
||||
(isMap(options[0]) ? options[0].get('value') : options[0].value) ??
|
||||
null,
|
||||
);
|
||||
const [sliderPosition, setSliderPosition] = useState(0);
|
||||
const [sliderHeight, setSliderHeight] = useState(0);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
@@ -36,15 +45,20 @@ export function VSegmentedControl({
|
||||
return (
|
||||
<div className={cx(styles.segmented_control, extendClassName)}>
|
||||
<div className={styles.options}>
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={`${index}_${option.value}`}
|
||||
className={cx(styles.option, isEqual(selected, option.value) && styles.selected)}
|
||||
ref={(el) => (optionsRef.current[index] = el!)}
|
||||
onClick={() => handleSelectAction(option.value, index)}>
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
{options.map((option, index) => {
|
||||
const label = isMap(option) ? option.get('label') : option.label;
|
||||
const value = isMap(option) ? option.get('value') : option.value;
|
||||
return (
|
||||
<div
|
||||
key={`${index}_${value}`}
|
||||
className={cx(styles.option, isEqual(selected, value) && styles.selected)}
|
||||
//@ts-expect-error TS2322
|
||||
ref={(el) => (optionsRef.current[index] = el!)}
|
||||
onClick={() => handleSelectAction(value, index)}>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{!isNil(selected) && (
|
||||
<div
|
||||
className={styles.slider}
|
||||
@@ -53,6 +67,7 @@ export function VSegmentedControl({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isNil(name) && <input type="hidden" name={name} value={selected} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
28
src/hooks/useCopy.ts
Normal file
28
src/hooks/useCopy.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { isEmpty, isNil } from 'lodash-es';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { NotificationType, useNotification } from '../components/Notifications';
|
||||
|
||||
export function useCopy() {
|
||||
const { showToast } = useNotification();
|
||||
const [cpState, copyToClipboard] = useCopyToClipboard();
|
||||
const copyAction = useCallback((content?: string | null) => {
|
||||
if (isNil(content) || isEmpty(content)) return;
|
||||
copyToClipboard(content);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isNil(cpState.error)) {
|
||||
showToast(NotificationType.ERROR, 'Failed to copy to clipboard', 'tabler:alert-circle', 3000);
|
||||
} else if (!isNil(cpState.value)) {
|
||||
showToast(
|
||||
NotificationType.SUCCESS,
|
||||
`Content copied to clipboard.`,
|
||||
'tabler:circle-check',
|
||||
3000,
|
||||
);
|
||||
}
|
||||
}, [cpState]);
|
||||
|
||||
return copyAction;
|
||||
}
|
@@ -22,13 +22,17 @@ export type MaterialDesign2Scheme = {
|
||||
};
|
||||
|
||||
export type MaterialDesign2SchemeSource = {
|
||||
primary: string;
|
||||
secondary: string;
|
||||
error: string;
|
||||
custom_colors: Record<string, string>;
|
||||
primary: string | null;
|
||||
secondary: string | null;
|
||||
error: string | null;
|
||||
custom_colors?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type MaterialDesign2SchemeStorage = {
|
||||
source: MaterialDesign2SchemeSource;
|
||||
scheme: MaterialDesign2Scheme;
|
||||
source?: MaterialDesign2SchemeSource;
|
||||
scheme?: MaterialDesign2Scheme;
|
||||
cssVariables?: string;
|
||||
cssAutoSchemeVariables?: string;
|
||||
scssVariables?: string;
|
||||
jsVariables?: string;
|
||||
};
|
||||
|
@@ -46,12 +46,34 @@ export type MaterialDesign3Scheme = {
|
||||
};
|
||||
|
||||
export type MaterialDesign3SchemeSource = {
|
||||
source: string;
|
||||
error: string;
|
||||
custom_colors: Record<string, string>;
|
||||
source: string | null;
|
||||
error: string | null;
|
||||
custom_colors?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type MaterialDesign3SchemeStorage = {
|
||||
source: MaterialDesign3SchemeSource;
|
||||
scheme: MaterialDesign3Scheme;
|
||||
source?: MaterialDesign3SchemeSource;
|
||||
scheme?: MaterialDesign3Scheme;
|
||||
cssVariables?: string;
|
||||
cssAutoSchemeVariables?: string;
|
||||
scssVariables?: string;
|
||||
jsVariables?: string;
|
||||
};
|
||||
|
||||
export type MaterialDesign3DynamicSchemeSource = {
|
||||
source: string | null;
|
||||
error: string | null;
|
||||
custom_colors?: Record<string, string>;
|
||||
variant: number | null;
|
||||
contrastLevel: number | null;
|
||||
harmonizeCustoms: boolean | null;
|
||||
};
|
||||
|
||||
export type MaterialDesign3DynamicSchemeStorage = {
|
||||
source?: MaterialDesign3DynamicSchemeSource;
|
||||
scheme?: MaterialDesign3Scheme;
|
||||
cssVariables?: string;
|
||||
cssAutoSchemeVariables?: string;
|
||||
scssVariables?: string;
|
||||
jsVariables?: string;
|
||||
};
|
||||
|
@@ -1,13 +1,19 @@
|
||||
import { find, isNil } from 'lodash-es';
|
||||
import { MaterialDesign2SchemeStorage } from './material-2-scheme';
|
||||
import { MaterialDesign3SchemeStorage } from './material-3-scheme';
|
||||
import {
|
||||
MaterialDesign3DynamicSchemeStorage,
|
||||
MaterialDesign3SchemeStorage,
|
||||
} from './material-3-scheme';
|
||||
import { Q2SchemeStorage } from './q-2-scheme';
|
||||
import { QSchemeStorage } from './q-scheme';
|
||||
import { SwatchSchemeStorage } from './swatch_scheme';
|
||||
|
||||
export type Option = {
|
||||
label: string;
|
||||
value: string | number | null;
|
||||
};
|
||||
export type Option<T = string | number | null> =
|
||||
| {
|
||||
label: string;
|
||||
value: T;
|
||||
}
|
||||
| Record<'label' | 'value', T>;
|
||||
|
||||
export type HarmonyColor = {
|
||||
color: string;
|
||||
@@ -27,7 +33,13 @@ export type ColorDescription = {
|
||||
oklch: [number, number, number];
|
||||
};
|
||||
|
||||
export type SchemeType = 'q_scheme' | 'swatch_scheme' | 'material_2' | 'material_3';
|
||||
export type SchemeType =
|
||||
| 'q_scheme'
|
||||
| 'q_2_scheme'
|
||||
| 'swatch_scheme'
|
||||
| 'material_2'
|
||||
| 'material_3'
|
||||
| 'material_3_dynamic';
|
||||
export type SchemeTypeOption = {
|
||||
label: string;
|
||||
short: string;
|
||||
@@ -35,9 +47,11 @@ export type SchemeTypeOption = {
|
||||
};
|
||||
export const SchemeTypeOptions: SchemeTypeOption[] = [
|
||||
{ label: 'Q Scheme', short: 'Q', value: 'q_scheme' },
|
||||
{ label: 'Q2 Scheme', short: 'Q2', value: 'q_2_scheme' },
|
||||
{ label: 'Swatch Scheme', short: 'Swatch', value: 'swatch_scheme' },
|
||||
{ label: 'Material Design 2 Scheme', short: 'M2', value: 'material_2' },
|
||||
{ label: 'Material Design 3 Scheme', short: 'M3', value: 'material_3' },
|
||||
{ label: 'Material Design 3 Dynamic Scheme', short: 'M3D', value: 'material_3_dynamic' },
|
||||
];
|
||||
|
||||
export function schemeType(
|
||||
@@ -47,7 +61,7 @@ export function schemeType(
|
||||
const useShort = short ?? false;
|
||||
const foundType = find(SchemeTypeOptions, { value }) as SchemeTypeOption | undefined;
|
||||
if (isNil(foundType)) {
|
||||
return null;
|
||||
return 'CORRUPTED';
|
||||
}
|
||||
return useShort ? foundType.short : foundType.label;
|
||||
}
|
||||
@@ -68,6 +82,8 @@ export type ColorShifting = {
|
||||
|
||||
export type SchemeStorage =
|
||||
| QSchemeStorage
|
||||
| Q2SchemeStorage
|
||||
| SwatchSchemeStorage
|
||||
| MaterialDesign2SchemeStorage
|
||||
| MaterialDesign3SchemeStorage;
|
||||
| MaterialDesign3SchemeStorage
|
||||
| MaterialDesign3DynamicSchemeStorage;
|
||||
|
@@ -8,7 +8,6 @@
|
||||
line-height: var(--font-size-xxs);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--border-radius-xs);
|
||||
cursor: pointer;
|
||||
}
|
||||
.color_block {
|
||||
width: 100%;
|
||||
@@ -19,6 +18,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xs) var(--spacing-s);
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
.title {
|
||||
display: flex;
|
||||
@@ -38,5 +38,6 @@
|
||||
}
|
||||
.color_value {
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { capitalize, isEmpty } from 'lodash-es';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import ContextMenu from '../../components/ContextMenu';
|
||||
import { useCopyColor } from '../../hooks/useCopyColor';
|
||||
import { ColorDescription } from '../../models';
|
||||
import styles from './ColorCard.module.css';
|
||||
@@ -57,7 +58,7 @@ export function ColorCard({ color, copyMode }: ColorCardProps) {
|
||||
}, [copytToClipboard, color, copyMode, colorHex]);
|
||||
|
||||
return (
|
||||
<div className={styles.card} onClick={handleCopy}>
|
||||
<div className={styles.card}>
|
||||
<div
|
||||
className={styles.color_block}
|
||||
style={{ backgroundColor: `rgb(${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})` }}
|
||||
@@ -69,7 +70,10 @@ export function ColorCard({ color, copyMode }: ColorCardProps) {
|
||||
<span className={styles.en_name}>{color.pinyin.map(capitalize).join(' ')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.color_value}>#{colorHex}</div>
|
||||
<div className={styles.color_value} onClick={handleCopy}>
|
||||
#{colorHex}
|
||||
</div>
|
||||
<ContextMenu color={colorHex} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -1,30 +1,19 @@
|
||||
import { Differ, HctDiffference } from 'color-module';
|
||||
import { useMemo } from 'react';
|
||||
import { HctDiffference } from '../../color_functions/color_module';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import styles from './CompareLayout.module.css';
|
||||
import { CompareMethodProps } from './share-props';
|
||||
|
||||
const defaultCompareResult: HctDiffference = {
|
||||
hue: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
chroma: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
lightness: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function HCTCompare({
|
||||
basic = '000000',
|
||||
compare = '000000',
|
||||
mode = 'absolute',
|
||||
}: CompareMethodProps) {
|
||||
const { colorFn } = useColorFunction();
|
||||
const defaultCompareResult: HctDiffference = useMemo(
|
||||
() => new HctDiffference(new Differ(0, 0), new Differ(0, 0), new Differ(0, 0)),
|
||||
[],
|
||||
);
|
||||
const differ = useMemo(() => {
|
||||
if (!colorFn) {
|
||||
return defaultCompareResult;
|
||||
|
@@ -1,30 +1,19 @@
|
||||
import { Differ, HSLDifference } from 'color-module';
|
||||
import { useMemo } from 'react';
|
||||
import { HSLDifference } from '../../color_functions/color_module';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import styles from './CompareLayout.module.css';
|
||||
import { CompareMethodProps } from './share-props';
|
||||
|
||||
const defaultCompareResult: HSLDifference = {
|
||||
hue: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
saturation: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
lightness: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function HSLCompare({
|
||||
basic = '000000',
|
||||
compare = '000000',
|
||||
mode = 'absolute',
|
||||
}: CompareMethodProps) {
|
||||
const { colorFn } = useColorFunction();
|
||||
const defaultCompareResult: HSLDifference = useMemo(
|
||||
() => new HSLDifference(new Differ(0, 0), new Differ(0, 0), new Differ(0, 0)),
|
||||
[],
|
||||
);
|
||||
const differ = useMemo(() => {
|
||||
if (!colorFn) {
|
||||
return defaultCompareResult;
|
||||
|
@@ -1,30 +1,19 @@
|
||||
import { Differ, OklchDifference } from 'color-module';
|
||||
import { useMemo } from 'react';
|
||||
import { OklchDifference } from '../../color_functions/color_module';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import styles from './CompareLayout.module.css';
|
||||
import { CompareMethodProps } from './share-props';
|
||||
|
||||
const defaultCompareResult: OklchDifference = {
|
||||
hue: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
chroma: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
lightness: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function OklchCompare({
|
||||
basic = '000000',
|
||||
compare = '000000',
|
||||
mode = 'absolute',
|
||||
}: CompareMethodProps) {
|
||||
const { colorFn } = useColorFunction();
|
||||
const defaultCompareResult: OklchDifference = useMemo(
|
||||
() => new OklchDifference(new Differ(0, 0), new Differ(0, 0), new Differ(0, 0)),
|
||||
[],
|
||||
);
|
||||
const differ = useMemo(() => {
|
||||
if (!colorFn) {
|
||||
return defaultCompareResult;
|
||||
|
@@ -1,30 +1,19 @@
|
||||
import { Differ, RGBDifference } from 'color-module';
|
||||
import { useMemo } from 'react';
|
||||
import { RGBDifference } from '../../color_functions/color_module';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import styles from './CompareLayout.module.css';
|
||||
import { CompareMethodProps } from './share-props';
|
||||
|
||||
const defaultCompareResult: RGBDifference = {
|
||||
r: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
g: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
b: {
|
||||
delta: 0,
|
||||
percent: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function RGBCompare({
|
||||
basic = '000000',
|
||||
compare = '000000',
|
||||
mode = 'absolute',
|
||||
}: CompareMethodProps) {
|
||||
const { colorFn } = useColorFunction();
|
||||
const defaultCompareResult: RGBDifference = useMemo(
|
||||
() => new RGBDifference(new Differ(0, 0), new Differ(0, 0), new Differ(0, 0)),
|
||||
[],
|
||||
);
|
||||
const differ = useMemo(() => {
|
||||
if (!colorFn) {
|
||||
return defaultCompareResult;
|
||||
|
@@ -1,19 +1,13 @@
|
||||
import cx from 'clsx';
|
||||
import { MixReversing } from 'color-module';
|
||||
import { useMemo } from 'react';
|
||||
import { MixReversing } from '../../color_functions/color_module';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import styles from './CompareLayout.module.css';
|
||||
import { CompareMethodProps } from './share-props';
|
||||
|
||||
const defaultMixResult: MixReversing = {
|
||||
r_factor: 0,
|
||||
g_factor: 0,
|
||||
b_factor: 0,
|
||||
average: 0,
|
||||
};
|
||||
|
||||
export function ShadeScale({ basic = '000000', compare = '000000' }: CompareMethodProps) {
|
||||
const { colorFn } = useColorFunction();
|
||||
const defaultMixResult: MixReversing = useMemo(() => new MixReversing(0, 0, 0, 0), []);
|
||||
const mixFactors = useMemo(() => {
|
||||
if (!colorFn) {
|
||||
return defaultMixResult;
|
||||
|
@@ -1,19 +1,13 @@
|
||||
import cx from 'clsx';
|
||||
import { MixReversing } from 'color-module';
|
||||
import { useMemo } from 'react';
|
||||
import { MixReversing } from '../../color_functions/color_module';
|
||||
import { useColorFunction } from '../../ColorFunctionContext';
|
||||
import styles from './CompareLayout.module.css';
|
||||
import { CompareMethodProps } from './share-props';
|
||||
|
||||
const defaultMixResult: MixReversing = {
|
||||
r_factor: 0,
|
||||
g_factor: 0,
|
||||
b_factor: 0,
|
||||
average: 0,
|
||||
};
|
||||
|
||||
export function TintScale({ basic = '000000', compare = '000000' }: CompareMethodProps) {
|
||||
const { colorFn } = useColorFunction();
|
||||
const defaultMixResult: MixReversing = useMemo(() => new MixReversing(0, 0, 0, 0), []);
|
||||
const mixFactors = useMemo(() => {
|
||||
if (!colorFn) {
|
||||
return defaultMixResult;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
@layer pages {
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 var(--spacing-m);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -9,6 +10,7 @@
|
||||
font-size: var(--font-size-m);
|
||||
}
|
||||
.color_blocks {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
@@ -16,6 +18,7 @@
|
||||
flex-wrap: nowrap;
|
||||
gap: var(--spacing-s);
|
||||
.color_block {
|
||||
max-height: 23em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
@@ -42,14 +45,25 @@
|
||||
padding-inline: var(--spacing-s);
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
.color_code {
|
||||
.color_code_row {
|
||||
height: 1.5em;
|
||||
padding-inline: var(--spacing-s);
|
||||
font-size: var(--font-size-s);
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
> span {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: var(--spacing-xs);
|
||||
overflow: visible;
|
||||
.color_code {
|
||||
height: 1.5em;
|
||||
padding-inline: var(--spacing-s);
|
||||
font-size: var(--font-size-s);
|
||||
text-transform: uppercase;
|
||||
text-align: right;
|
||||
> span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import cx from 'clsx';
|
||||
import { constant, flatten, isEqual, take, times } from 'lodash-es';
|
||||
import { useMemo } from 'react';
|
||||
import ContextMenu from '../../components/ContextMenu';
|
||||
import { useCopyColor } from '../../hooks/useCopyColor';
|
||||
import { HarmonyColor } from '../../models';
|
||||
import styles from './HarmonyPreview.module.css';
|
||||
@@ -33,8 +34,11 @@ export function HarmonyPreview({ colors = [] }: HarmonyPreviewProps) {
|
||||
style={{ flexGrow: ratio }}>
|
||||
<div className={styles.color_ratio}>{ratio > 0 && `Ratio: ${ratio}`}</div>
|
||||
<div className={styles.color_square} style={{ backgroundColor: `#${color}` }}></div>
|
||||
<div className={styles.color_code}>
|
||||
{ratio > 0 && <span onClick={() => copyColor(color)}>#{color}</span>}
|
||||
<div className={styles.color_code_row}>
|
||||
<div className={styles.color_code}>
|
||||
{ratio > 0 && <span onClick={() => copyColor(color)}>#{color}</span>}
|
||||
</div>
|
||||
<ContextMenu color={color} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
@@ -23,18 +23,18 @@ export function Darkens({ color, darkens, mix, step, maximum, copyMode }: Darken
|
||||
switch (mix) {
|
||||
case 'progressive':
|
||||
for (let i = 1; i <= darkens; i++) {
|
||||
const darkenColor = colorFn.darken(last(darkenColors), step);
|
||||
const darkenColor = colorFn.darken(last(darkenColors) ?? '', step ?? 0);
|
||||
darkenColors.push(darkenColor);
|
||||
}
|
||||
break;
|
||||
case 'linear':
|
||||
for (let i = 1; i <= darkens; i++) {
|
||||
const darkenColor = colorFn.darken(color, step * i);
|
||||
const darkenColor = colorFn.darken(color, (step ?? 0) * i);
|
||||
darkenColors.push(darkenColor);
|
||||
}
|
||||
break;
|
||||
case 'average': {
|
||||
const interval = maximum / darkens / 100;
|
||||
const interval = (maximum ?? 0) / darkens / 100;
|
||||
for (let i = 1; i <= darkens; i++) {
|
||||
const darkenColor = colorFn.darken(color, interval * i);
|
||||
darkenColors.push(darkenColor);
|
||||
|
@@ -23,18 +23,18 @@ export function Lightens({ color, lightens, mix, step, maximum, copyMode }: Ligh
|
||||
switch (mix) {
|
||||
case 'progressive':
|
||||
for (let i = 1; i <= lightens; i++) {
|
||||
const lightenColor = colorFn.lighten(last(lightenColors), step);
|
||||
const lightenColor = colorFn.lighten(last(lightenColors) ?? '', step ?? 0);
|
||||
lightenColors.push(lightenColor);
|
||||
}
|
||||
break;
|
||||
case 'linear':
|
||||
for (let i = 1; i <= lightens; i++) {
|
||||
const lightenColor = colorFn.lighten(color, step * i);
|
||||
const lightenColor = colorFn.lighten(color, (step ?? 0) * i);
|
||||
lightenColors.push(lightenColor);
|
||||
}
|
||||
break;
|
||||
case 'average': {
|
||||
const interval = maximum / lightens / 100;
|
||||
const interval = (maximum ?? 0) / lightens / 100;
|
||||
for (let i = 1; i <= lightens; i++) {
|
||||
const lightenColor = colorFn.lighten(color, interval * i);
|
||||
lightenColors.push(lightenColor);
|
||||
|
13
src/page-components/scheme/ColorEntry.module.css
Normal file
13
src/page-components/scheme/ColorEntry.module.css
Normal file
@@ -0,0 +1,13 @@
|
||||
@layer components {
|
||||
.delete_btn {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-yuebai);
|
||||
background-color: oklch(from var(--color-danger) l c h / 0.25);
|
||||
&:hover {
|
||||
background-color: oklch(from var(--color-danger-hover) l c h / 0.65);
|
||||
}
|
||||
&:active {
|
||||
background-color: oklch(from var(--color-danger-active) l c h / 0.65);
|
||||
}
|
||||
}
|
||||
}
|
38
src/page-components/scheme/ColorEntry.tsx
Normal file
38
src/page-components/scheme/ColorEntry.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ActionIcon } from '../../components/ActionIcon';
|
||||
import { FloatColorPicker } from '../../components/FloatColorPicker';
|
||||
import styles from './colorEntry.module.css';
|
||||
|
||||
export type IdenticalColorEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
type ColorEntryProps = {
|
||||
entry: IdenticalColorEntry;
|
||||
onDelete?: (index: string) => void;
|
||||
};
|
||||
|
||||
export function ColorEntry({ entry, onDelete }: ColorEntryProps) {
|
||||
return (
|
||||
<>
|
||||
<div className="input_wrapper">
|
||||
<input type="text" name={`name_${entry.id}`} defaultValue={entry.name} />
|
||||
</div>
|
||||
<div>
|
||||
<FloatColorPicker
|
||||
name={`color_${entry.id}`}
|
||||
color={isEmpty(entry.color) ? undefined : entry.color}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ActionIcon
|
||||
icon="tabler:trash"
|
||||
extendClassName={styles.delete_btn}
|
||||
onClick={() => onDelete?.(entry.id)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
6
src/page-components/scheme/CorruptedScheme.module.css
Normal file
6
src/page-components/scheme/CorruptedScheme.module.css
Normal file
@@ -0,0 +1,6 @@
|
||||
@layer pages {
|
||||
.corrupted {
|
||||
font-size: var(--font-size-xl);
|
||||
color: var(--color-danger);
|
||||
}
|
||||
}
|
8
src/page-components/scheme/CorruptedScheme.tsx
Normal file
8
src/page-components/scheme/CorruptedScheme.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import styles from './CorruptedScheme.module.css';
|
||||
export function CorruptedScheme() {
|
||||
return (
|
||||
<div className="center">
|
||||
<div className={styles.corrupted}>Unrecognizable or corrupted scheme</div>
|
||||
</div>
|
||||
);
|
||||
}
|
16
src/page-components/scheme/Export.module.css
Normal file
16
src/page-components/scheme/Export.module.css
Normal file
@@ -0,0 +1,16 @@
|
||||
@layer pages {
|
||||
.export_layout {
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-s);
|
||||
.tools {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
}
|
||||
}
|
53
src/page-components/scheme/Export.tsx
Normal file
53
src/page-components/scheme/Export.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { HSegmentedControl } from '../../components/HSegmentedControl';
|
||||
import { Labeled } from '../../components/Labeled';
|
||||
import { ScrollArea } from '../../components/ScrollArea';
|
||||
import { useCopy } from '../../hooks/useCopy';
|
||||
import { Option, SchemeContent, SchemeStorage } from '../../models';
|
||||
import styles from './Export.module.css';
|
||||
|
||||
const exportOptions: Option[] = [
|
||||
{ label: 'CSS', value: 'css' },
|
||||
{ label: 'CSS Auto Scheme', value: 'css-auto' },
|
||||
{ label: 'SCSS', value: 'scss' },
|
||||
{ label: 'Javascript Object', value: 'js_object' },
|
||||
];
|
||||
|
||||
type SchemeExportProps = {
|
||||
scheme: SchemeContent<SchemeStorage>;
|
||||
};
|
||||
|
||||
export function SchemeExport({ scheme }: SchemeExportProps) {
|
||||
const [activeExport, setActiveExport] = useState<Option['value']>(exportOptions[0].value);
|
||||
const exportContent = useMemo(() => {
|
||||
switch (activeExport) {
|
||||
case 'css':
|
||||
return scheme.schemeStorage.cssVariables;
|
||||
case 'css-auto':
|
||||
return scheme.schemeStorage.cssAutoSchemeVariables;
|
||||
case 'scss':
|
||||
return scheme.schemeStorage.scssVariables;
|
||||
case 'js_object':
|
||||
return scheme.schemeStorage.jsVariables;
|
||||
}
|
||||
}, [scheme, activeExport]);
|
||||
const copyToClipboard = useCopy();
|
||||
|
||||
return (
|
||||
<ScrollArea enableY>
|
||||
<div className={styles.export_layout}>
|
||||
<div className={styles.tools}>
|
||||
<Labeled label="Export Options" inline>
|
||||
<HSegmentedControl
|
||||
options={exportOptions}
|
||||
value={activeExport}
|
||||
onChange={setActiveExport}
|
||||
/>
|
||||
</Labeled>
|
||||
<button onClick={() => copyToClipboard(exportContent)}>Copy</button>
|
||||
</div>
|
||||
<pre>{exportContent}</pre>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
}
|
44
src/page-components/scheme/M2Scheme.tsx
Normal file
44
src/page-components/scheme/M2Scheme.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useState } from 'react';
|
||||
import { Tab } from '../../components/Tab';
|
||||
import { MaterialDesign2SchemeStorage } from '../../material-2-scheme';
|
||||
import { SchemeContent } from '../../models';
|
||||
import { isNilOrEmpty } from '../../utls';
|
||||
import { SchemeExport } from './Export';
|
||||
import { M2SchemeBuilder } from './m2-scheme/Builder';
|
||||
import { M2SchemePreview } from './m2-scheme/Preview';
|
||||
|
||||
const tabOptions = [
|
||||
{ title: 'Overview', id: 'overview' },
|
||||
{ title: 'Builder', id: 'builder' },
|
||||
{ title: 'Exports', id: 'export' },
|
||||
];
|
||||
|
||||
type M2SchemeProps = {
|
||||
scheme: SchemeContent<MaterialDesign2SchemeStorage>;
|
||||
};
|
||||
|
||||
export function M2Scheme({ scheme }: M2SchemeProps) {
|
||||
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
|
||||
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tab
|
||||
tabs={tabOptions}
|
||||
activeTab={activeTab}
|
||||
onActive={(v) => setActiveTab(v as string)}
|
||||
disabled={{
|
||||
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
|
||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
||||
}}
|
||||
/>
|
||||
{isEqual(activeTab, 'overview') && <M2SchemePreview scheme={scheme} />}
|
||||
{isEqual(activeTab, 'builder') && (
|
||||
<M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />
|
||||
)}
|
||||
{isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
|
||||
</>
|
||||
);
|
||||
}
|
44
src/page-components/scheme/M3DynamicScheme.tsx
Normal file
44
src/page-components/scheme/M3DynamicScheme.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useState } from 'react';
|
||||
import { Tab } from '../../components/Tab';
|
||||
import { MaterialDesign3DynamicSchemeStorage } from '../../material-3-scheme';
|
||||
import { SchemeContent } from '../../models';
|
||||
import { isNilOrEmpty } from '../../utls';
|
||||
import { SchemeExport } from './Export';
|
||||
import { M3DynamicSchemeBuilder } from './m3-dynamic-scheme/Builder';
|
||||
import { M3SchemePreview } from './m3-scheme/Preview';
|
||||
|
||||
const tabOptions = [
|
||||
{ title: 'Overview', id: 'overview' },
|
||||
{ title: 'Builder', id: 'builder' },
|
||||
{ title: 'Exports', id: 'export' },
|
||||
];
|
||||
|
||||
type M3SchemeProps = {
|
||||
scheme: SchemeContent<MaterialDesign3DynamicSchemeStorage>;
|
||||
};
|
||||
|
||||
export function M3DynamicScheme({ scheme }: M3SchemeProps) {
|
||||
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
|
||||
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tab
|
||||
tabs={tabOptions}
|
||||
activeTab={activeTab}
|
||||
onActive={(v) => setActiveTab(v as string)}
|
||||
disabled={{
|
||||
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
|
||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
||||
}}
|
||||
/>
|
||||
{isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />}
|
||||
{isEqual(activeTab, 'builder') && (
|
||||
<M3DynamicSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||
)}
|
||||
{isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
|
||||
</>
|
||||
);
|
||||
}
|
44
src/page-components/scheme/M3Scheme.tsx
Normal file
44
src/page-components/scheme/M3Scheme.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useState } from 'react';
|
||||
import { Tab } from '../../components/Tab';
|
||||
import { MaterialDesign3SchemeStorage } from '../../material-3-scheme';
|
||||
import { SchemeContent } from '../../models';
|
||||
import { isNilOrEmpty } from '../../utls';
|
||||
import { SchemeExport } from './Export';
|
||||
import { M3SchemeBuilder } from './m3-scheme/Builder';
|
||||
import { M3SchemePreview } from './m3-scheme/Preview';
|
||||
|
||||
const tabOptions = [
|
||||
{ title: 'Overview', id: 'overview' },
|
||||
{ title: 'Builder', id: 'builder' },
|
||||
{ title: 'Exports', id: 'export' },
|
||||
];
|
||||
|
||||
type M3SchemeProps = {
|
||||
scheme: SchemeContent<MaterialDesign3SchemeStorage>;
|
||||
};
|
||||
|
||||
export function M3Scheme({ scheme }: M3SchemeProps) {
|
||||
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
|
||||
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tab
|
||||
tabs={tabOptions}
|
||||
activeTab={activeTab}
|
||||
onActive={(v) => setActiveTab(v as string)}
|
||||
disabled={{
|
||||
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
|
||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
||||
}}
|
||||
/>
|
||||
{isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />}
|
||||
{isEqual(activeTab, 'builder') && (
|
||||
<M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||
)}
|
||||
{isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
|
||||
</>
|
||||
);
|
||||
}
|
44
src/page-components/scheme/Q2Scheme.tsx
Normal file
44
src/page-components/scheme/Q2Scheme.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useState } from 'react';
|
||||
import { Tab } from '../../components/Tab';
|
||||
import { SchemeContent } from '../../models';
|
||||
import { Q2SchemeStorage } from '../../q-2-scheme';
|
||||
import { isNilOrEmpty } from '../../utls';
|
||||
import { SchemeExport } from './Export';
|
||||
import { Q2SchemeBuilder } from './q-2-scheme/Builder';
|
||||
import Q2SchemePreview from './q-2-scheme/Preview';
|
||||
|
||||
const tabOptions = [
|
||||
{ title: 'Overview', id: 'overview' },
|
||||
{ title: 'Builder', id: 'builder' },
|
||||
{ title: 'Exports', id: 'export' },
|
||||
];
|
||||
|
||||
type Q2SchemeProps = {
|
||||
scheme: SchemeContent<Q2SchemeStorage>;
|
||||
};
|
||||
|
||||
export function Q2Scheme({ scheme }: Q2SchemeProps) {
|
||||
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
|
||||
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tab
|
||||
tabs={tabOptions}
|
||||
activeTab={activeTab}
|
||||
onActive={(v) => setActiveTab(v as string)}
|
||||
disabled={{
|
||||
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
|
||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
||||
}}
|
||||
/>
|
||||
{isEqual(activeTab, 'overview') && <Q2SchemePreview scheme={scheme} />}
|
||||
{isEqual(activeTab, 'builder') && (
|
||||
<Q2SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||
)}
|
||||
{isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
|
||||
</>
|
||||
);
|
||||
}
|
44
src/page-components/scheme/QScheme.tsx
Normal file
44
src/page-components/scheme/QScheme.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { isEqual, isNil } from 'lodash-es';
|
||||
import { useState } from 'react';
|
||||
import { Tab } from '../../components/Tab';
|
||||
import { SchemeContent } from '../../models';
|
||||
import { QSchemeStorage } from '../../q-scheme';
|
||||
import { isNilOrEmpty } from '../../utls';
|
||||
import { SchemeExport } from './Export';
|
||||
import { QSchemeBuilder } from './q-scheme/Builder';
|
||||
import { QSchemePreview } from './q-scheme/Preview';
|
||||
|
||||
const tabOptions = [
|
||||
{ title: 'Overview', id: 'overview' },
|
||||
{ title: 'Builder', id: 'builder' },
|
||||
{ title: 'Exports', id: 'export' },
|
||||
];
|
||||
|
||||
type QSchemeProps = {
|
||||
scheme: SchemeContent<QSchemeStorage>;
|
||||
};
|
||||
|
||||
export function QScheme({ scheme }: QSchemeProps) {
|
||||
const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
|
||||
isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tab
|
||||
tabs={tabOptions}
|
||||
activeTab={activeTab}
|
||||
onActive={(v) => setActiveTab(v as string)}
|
||||
disabled={{
|
||||
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
|
||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
||||
}}
|
||||
/>
|
||||
{isEqual(activeTab, 'overview') && <QSchemePreview scheme={scheme} />}
|
||||
{isEqual(activeTab, 'builder') && (
|
||||
<QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||
)}
|
||||
{isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
|
||||
</>
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user