Compare commits
No commits in common. "main" and "wasm-load" have entirely different histories.
|
@ -9,8 +9,6 @@ crate-type = ["cdylib"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
color-name = "1.1.0"
|
color-name = "1.1.0"
|
||||||
enum-iterator = "2.1.0"
|
enum-iterator = "2.1.0"
|
||||||
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"] }
|
palette = { version = "0.7.6", features = ["serde"] }
|
||||||
serde = { version = "1.0.216", features = ["derive"] }
|
serde = { version = "1.0.216", features = ["derive"] }
|
||||||
serde-wasm-bindgen = "0.6.5"
|
serde-wasm-bindgen = "0.6.5"
|
||||||
|
|
|
@ -135,14 +135,3 @@ pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result<f32, err
|
||||||
.into_format::<f32>();
|
.into_format::<f32>();
|
||||||
Ok(fg_srgb.relative_contrast(bg_srgb))
|
Ok(fg_srgb.relative_contrast(bg_srgb))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! cond {
|
|
||||||
($s: expr, $a: expr, $b: expr) => {
|
|
||||||
if $s {
|
|
||||||
$a
|
|
||||||
} else {
|
|
||||||
$b
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use serde::{ser::SerializeStruct, Serialize};
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
|
|
||||||
use crate::{convert::map_hsl_to_srgb_hex, errors, schemes::material_design_2::swatch::M2Swatch};
|
use crate::{convert::map_hsl_to_srgb_hex, errors, schemes::material_design_2::swatch::M2Swatch};
|
||||||
|
@ -76,28 +75,12 @@ impl M2BaselineColors {
|
||||||
variable_lines.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
variable_lines.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
||||||
|
|
||||||
for (name, color_set) in &self.custom_colors {
|
for (name, color_set) in &self.custom_colors {
|
||||||
variable_lines.extend(color_set.to_css_variable(&prefix, &name.to_lowercase()));
|
variable_lines.extend(color_set.to_css_variable(&prefix, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
variable_lines
|
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> {
|
pub fn to_scss_variable(&self) -> Vec<String> {
|
||||||
let mut variable_lines = Vec::new();
|
let mut variable_lines = Vec::new();
|
||||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||||
|
@ -110,7 +93,7 @@ impl M2BaselineColors {
|
||||||
variable_lines.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
variable_lines.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
||||||
|
|
||||||
for (name, color_set) in &self.custom_colors {
|
for (name, color_set) in &self.custom_colors {
|
||||||
variable_lines.extend(color_set.to_scss_variable(&prefix, &name.to_lowercase()));
|
variable_lines.extend(color_set.to_scss_variable(&prefix, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
variable_lines
|
variable_lines
|
||||||
|
@ -128,7 +111,7 @@ impl M2BaselineColors {
|
||||||
variable_lines.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
variable_lines.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
||||||
|
|
||||||
for (name, color_set) in &self.custom_colors {
|
for (name, color_set) in &self.custom_colors {
|
||||||
variable_lines.extend(color_set.to_javascript_object(&prefix, &name.to_lowercase()));
|
variable_lines.extend(color_set.to_javascript_object(&prefix, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
variable_lines
|
variable_lines
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::{convert::map_hsl_to_srgb_hex, errors};
|
use crate::{convert::map_hsl_to_srgb_hex, errors};
|
||||||
|
@ -83,16 +82,6 @@ impl M2ColorSet {
|
||||||
variable_lines
|
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> {
|
pub fn to_scss_variable(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||||
let mut variable_lines = Vec::new();
|
let mut variable_lines = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use baseline::M2BaselineColors;
|
use baseline::M2BaselineColors;
|
||||||
use linked_hash_set::LinkedHashSet;
|
|
||||||
use palette::Hsl;
|
use palette::Hsl;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
@ -49,34 +48,6 @@ impl SchemeExport for MaterialDesign2Scheme {
|
||||||
css_variables.join("\n")
|
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 {
|
fn output_scss_variables(&self) -> String {
|
||||||
let mut scss_variables = Vec::new();
|
let mut scss_variables = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::convert::map_lch_to_srgb_hex;
|
use crate::convert::map_lch_to_srgb_hex;
|
||||||
|
@ -81,34 +80,6 @@ impl M3BaselineColors {
|
||||||
self.customs.insert(name, color_set_generator(c));
|
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> {
|
pub fn to_css_variables(&self) -> Vec<String> {
|
||||||
let mut css_variables = Vec::new();
|
let mut css_variables = Vec::new();
|
||||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||||
|
@ -126,31 +97,12 @@ impl M3BaselineColors {
|
||||||
css_variables.push(format!("--color-{}-scrim: #{};", prefix, self.scrim));
|
css_variables.push(format!("--color-{}-scrim: #{};", prefix, self.scrim));
|
||||||
css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
css_variables.push(format!("--color-{}-shadow: #{};", prefix, self.shadow));
|
||||||
for (name, color_set) in &self.customs {
|
for (name, color_set) in &self.customs {
|
||||||
css_variables.extend(color_set.to_css_variables(prefix, &name.to_lowercase()));
|
css_variables.extend(color_set.to_css_variables(prefix, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
css_variables
|
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> {
|
pub fn to_scss_variables(&self) -> Vec<String> {
|
||||||
let mut scss_variables = Vec::new();
|
let mut scss_variables = Vec::new();
|
||||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||||
|
@ -168,7 +120,7 @@ impl M3BaselineColors {
|
||||||
scss_variables.push(format!("$color-{}-scrim: #{};", prefix, self.scrim));
|
scss_variables.push(format!("$color-{}-scrim: #{};", prefix, self.scrim));
|
||||||
scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
scss_variables.push(format!("$color-{}-shadow: #{};", prefix, self.shadow));
|
||||||
for (name, color_set) in &self.customs {
|
for (name, color_set) in &self.customs {
|
||||||
scss_variables.extend(color_set.to_scss_variables(prefix, &name.to_lowercase()));
|
scss_variables.extend(color_set.to_scss_variables(prefix, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
scss_variables
|
scss_variables
|
||||||
|
@ -197,8 +149,7 @@ impl M3BaselineColors {
|
||||||
js_object_fields.push(format!("{}Scrim: '#{}',", prefix, self.scrim));
|
js_object_fields.push(format!("{}Scrim: '#{}',", prefix, self.scrim));
|
||||||
js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
js_object_fields.push(format!("{}Shadow: '#{}',", prefix, self.shadow));
|
||||||
for (name, color_set) in &self.customs {
|
for (name, color_set) in &self.customs {
|
||||||
js_object_fields
|
js_object_fields.extend(color_set.to_javascript_object_fields(prefix, name));
|
||||||
.extend(color_set.to_javascript_object_fields(prefix, &name.to_lowercase()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
js_object_fields
|
js_object_fields
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::convert::map_lch_to_srgb_hex;
|
use crate::convert::map_lch_to_srgb_hex;
|
||||||
|
@ -107,25 +106,6 @@ impl M3ColorSet {
|
||||||
variable_lines
|
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> {
|
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||||
let mut variable_lines = Vec::new();
|
let mut variable_lines = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub use baseline::M3BaselineColors;
|
use baseline::M3BaselineColors;
|
||||||
pub use color_set::M3ColorSet;
|
|
||||||
use linked_hash_set::LinkedHashSet;
|
|
||||||
use palette::{IntoColor, Lch, Srgb};
|
use palette::{IntoColor, Lch, Srgb};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
pub use surface::M3SurfaceSet;
|
use tonal_palette::TonalPalette;
|
||||||
pub use swatch::M3PaletteSwatch;
|
|
||||||
pub use tonal_palette::TonalPalette;
|
|
||||||
|
|
||||||
use crate::convert::map_lch_to_srgb_hex;
|
use crate::convert::map_lch_to_srgb_hex;
|
||||||
use crate::errors;
|
use crate::errors;
|
||||||
|
@ -18,7 +13,6 @@ use super::SchemeExport;
|
||||||
mod baseline;
|
mod baseline;
|
||||||
mod color_set;
|
mod color_set;
|
||||||
mod surface;
|
mod surface;
|
||||||
mod swatch;
|
|
||||||
mod tonal_palette;
|
mod tonal_palette;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
@ -27,7 +21,6 @@ pub struct MaterialDesign3Scheme {
|
||||||
pub black: String,
|
pub black: String,
|
||||||
pub light_baseline: M3BaselineColors,
|
pub light_baseline: M3BaselineColors,
|
||||||
pub dark_baseline: M3BaselineColors,
|
pub dark_baseline: M3BaselineColors,
|
||||||
pub swatches: HashMap<String, M3PaletteSwatch>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialDesign3Scheme {
|
impl MaterialDesign3Scheme {
|
||||||
|
@ -48,20 +41,11 @@ impl MaterialDesign3Scheme {
|
||||||
let nv = TonalPalette::from_hue_and_chroma(source_hue, (source.chroma / 6.0).min(8.0));
|
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 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 {
|
Ok(Self {
|
||||||
white: map_lch_to_srgb_hex(&Lch::new(100.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)),
|
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),
|
light_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, false),
|
||||||
dark_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, true),
|
dark_baseline: M3BaselineColors::new(&p, &s, &t, &n, &nv, &e, true),
|
||||||
swatches,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,23 +62,8 @@ impl MaterialDesign3Scheme {
|
||||||
let palette = TonalPalette::from_hue_and_chroma(hue, custom_color.chroma);
|
let palette = TonalPalette::from_hue_and_chroma(hue, custom_color.chroma);
|
||||||
self.light_baseline.add_custom_set(name.clone(), &palette);
|
self.light_baseline.add_custom_set(name.clone(), &palette);
|
||||||
self.dark_baseline.add_custom_set(name.clone(), &palette);
|
self.dark_baseline.add_custom_set(name.clone(), &palette);
|
||||||
self.swatches.insert(name, M3PaletteSwatch::new(&palette));
|
|
||||||
Ok(())
|
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 {
|
impl SchemeExport for MaterialDesign3Scheme {
|
||||||
|
@ -105,44 +74,10 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||||
css_variables.push(format!("--color-black: #{};", self.black));
|
css_variables.push(format!("--color-black: #{};", self.black));
|
||||||
css_variables.extend(self.light_baseline.to_css_variables());
|
css_variables.extend(self.light_baseline.to_css_variables());
|
||||||
css_variables.extend(self.dark_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")
|
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 {
|
fn output_scss_variables(&self) -> String {
|
||||||
let mut scss_variables = Vec::new();
|
let mut scss_variables = Vec::new();
|
||||||
|
|
||||||
|
@ -150,9 +85,6 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||||
scss_variables.push(format!("$color-black: #{};", self.black));
|
scss_variables.push(format!("$color-black: #{};", self.black));
|
||||||
scss_variables.extend(self.light_baseline.to_scss_variables());
|
scss_variables.extend(self.light_baseline.to_scss_variables());
|
||||||
scss_variables.extend(self.dark_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")
|
scss_variables.join("\n")
|
||||||
}
|
}
|
||||||
|
@ -168,7 +100,8 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||||
self.light_baseline
|
self.light_baseline
|
||||||
.to_javascript_object_fields()
|
.to_javascript_object_fields()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| format!(" {}", s)),
|
.map(|s| format!(" {}", s))
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
);
|
);
|
||||||
js_object.push(" },".to_string());
|
js_object.push(" },".to_string());
|
||||||
js_object.push(" dark: {".to_string());
|
js_object.push(" dark: {".to_string());
|
||||||
|
@ -176,19 +109,10 @@ impl SchemeExport for MaterialDesign3Scheme {
|
||||||
self.dark_baseline
|
self.dark_baseline
|
||||||
.to_javascript_object_fields()
|
.to_javascript_object_fields()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|s| format!(" {}", s)),
|
.map(|s| format!(" {}", s))
|
||||||
|
.collect::<Vec<String>>(),
|
||||||
);
|
);
|
||||||
js_object.push(" },".to_string());
|
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.push("}".to_string());
|
||||||
|
|
||||||
js_object.join("\n")
|
js_object.join("\n")
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::convert::map_lch_to_srgb_hex;
|
use crate::convert::map_lch_to_srgb_hex;
|
||||||
|
@ -10,7 +9,6 @@ pub struct M3SurfaceSet {
|
||||||
pub root: String,
|
pub root: String,
|
||||||
pub dim: String,
|
pub dim: String,
|
||||||
pub bright: String,
|
pub bright: String,
|
||||||
pub variant: String,
|
|
||||||
pub container: String,
|
pub container: String,
|
||||||
pub container_lowest: String,
|
pub container_lowest: String,
|
||||||
pub container_low: String,
|
pub container_low: String,
|
||||||
|
@ -27,7 +25,6 @@ impl M3SurfaceSet {
|
||||||
let root = neutral.tone(98.0);
|
let root = neutral.tone(98.0);
|
||||||
let dim = neutral.tone(87.0);
|
let dim = neutral.tone(87.0);
|
||||||
let bright = neutral.tone(98.0);
|
let bright = neutral.tone(98.0);
|
||||||
let variant = neutral_variant.tone(90.0);
|
|
||||||
let container = neutral.tone(94.0);
|
let container = neutral.tone(94.0);
|
||||||
let container_lowest = neutral.tone(100.0);
|
let container_lowest = neutral.tone(100.0);
|
||||||
let container_low = neutral.tone(96.0);
|
let container_low = neutral.tone(96.0);
|
||||||
|
@ -42,7 +39,6 @@ impl M3SurfaceSet {
|
||||||
root: map_lch_to_srgb_hex(&root),
|
root: map_lch_to_srgb_hex(&root),
|
||||||
dim: map_lch_to_srgb_hex(&dim),
|
dim: map_lch_to_srgb_hex(&dim),
|
||||||
bright: map_lch_to_srgb_hex(&bright),
|
bright: map_lch_to_srgb_hex(&bright),
|
||||||
variant: map_lch_to_srgb_hex(&variant),
|
|
||||||
container: map_lch_to_srgb_hex(&container),
|
container: map_lch_to_srgb_hex(&container),
|
||||||
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
||||||
container_low: map_lch_to_srgb_hex(&container_low),
|
container_low: map_lch_to_srgb_hex(&container_low),
|
||||||
|
@ -59,7 +55,6 @@ impl M3SurfaceSet {
|
||||||
let root = neutral.tone(6.0);
|
let root = neutral.tone(6.0);
|
||||||
let dim = neutral.tone(6.0);
|
let dim = neutral.tone(6.0);
|
||||||
let bright = neutral.tone(24.0);
|
let bright = neutral.tone(24.0);
|
||||||
let variant = neutral_variant.tone(30.0);
|
|
||||||
let container = neutral.tone(12.0);
|
let container = neutral.tone(12.0);
|
||||||
let container_lowest = neutral.tone(4.0);
|
let container_lowest = neutral.tone(4.0);
|
||||||
let container_low = neutral.tone(10.0);
|
let container_low = neutral.tone(10.0);
|
||||||
|
@ -74,7 +69,6 @@ impl M3SurfaceSet {
|
||||||
root: map_lch_to_srgb_hex(&root),
|
root: map_lch_to_srgb_hex(&root),
|
||||||
dim: map_lch_to_srgb_hex(&dim),
|
dim: map_lch_to_srgb_hex(&dim),
|
||||||
bright: map_lch_to_srgb_hex(&bright),
|
bright: map_lch_to_srgb_hex(&bright),
|
||||||
variant: map_lch_to_srgb_hex(&variant),
|
|
||||||
container: map_lch_to_srgb_hex(&container),
|
container: map_lch_to_srgb_hex(&container),
|
||||||
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
||||||
container_low: map_lch_to_srgb_hex(&container_low),
|
container_low: map_lch_to_srgb_hex(&container_low),
|
||||||
|
@ -90,126 +84,89 @@ impl M3SurfaceSet {
|
||||||
pub fn to_css_variables(&self, prefix: &str) -> Vec<String> {
|
pub fn to_css_variables(&self, prefix: &str) -> Vec<String> {
|
||||||
let mut css_variables = Vec::new();
|
let mut css_variables = Vec::new();
|
||||||
|
|
||||||
css_variables.push(format!("--color-{}-surface: #{};", prefix, self.root));
|
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-dim: ${};", prefix, self.dim));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-surface-bright: #{};",
|
"--color-{}-surface-bright: ${};",
|
||||||
prefix, self.bright
|
prefix, self.bright
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-surface-variant: #{};",
|
"--color-{}-surface-container: ${};",
|
||||||
prefix, self.variant
|
|
||||||
));
|
|
||||||
css_variables.push(format!(
|
|
||||||
"--color-{}-surface-container: #{};",
|
|
||||||
prefix, self.container
|
prefix, self.container
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-surface-container-lowest: #{};",
|
"--color-{}-surface-container-lowest: ${};",
|
||||||
prefix, self.container_lowest
|
prefix, self.container_lowest
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-surface-container-low: #{};",
|
"--color-{}-surface-container-low: ${};",
|
||||||
prefix, self.container_low
|
prefix, self.container_low
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-surface-container-high: #{};",
|
"--color-{}-surface-container-high: ${};",
|
||||||
prefix, self.container_high
|
prefix, self.container_high
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-surface-container-highest: #{};",
|
"--color-{}-surface-container-highest: ${};",
|
||||||
prefix, self.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!(
|
css_variables.push(format!(
|
||||||
"--color-{}-on-surface-variant: #{};",
|
"--color-{}-on-surface-variant: ${};",
|
||||||
prefix, self.on_root_variant
|
prefix, self.on_root_variant
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-inverse-surface: #{};",
|
"--color-{}-inverse-surface: ${};",
|
||||||
prefix, self.inverse
|
prefix, self.inverse
|
||||||
));
|
));
|
||||||
css_variables.push(format!(
|
css_variables.push(format!(
|
||||||
"--color-{}-inverse-on-surface: #{};",
|
"--color-{}-inverse-on-surface: ${};",
|
||||||
prefix, self.on_inverse
|
prefix, self.on_inverse
|
||||||
));
|
));
|
||||||
|
|
||||||
css_variables
|
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> {
|
pub fn to_scss_variables(&self, prefix: &str) -> Vec<String> {
|
||||||
let mut scss_variables = Vec::new();
|
let mut scss_variables = Vec::new();
|
||||||
|
|
||||||
scss_variables.push(format!("$color-{}-surface: #{};", prefix, self.root));
|
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-dim: ${};", prefix, self.dim));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-surface-bright: #{};",
|
"$color-{}-surface-bright: ${};",
|
||||||
prefix, self.bright
|
prefix, self.bright
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-surface-variant: #{};",
|
"$color-{}-surface-container: ${};",
|
||||||
prefix, self.variant
|
|
||||||
));
|
|
||||||
scss_variables.push(format!(
|
|
||||||
"$color-{}-surface-container: #{};",
|
|
||||||
prefix, self.container
|
prefix, self.container
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-surface-container-lowest: #{};",
|
"$color-{}-surface-container-lowest: ${};",
|
||||||
prefix, self.container_lowest
|
prefix, self.container_lowest
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-surface-container-low: #{};",
|
"$color-{}-surface-container-low: ${};",
|
||||||
prefix, self.container_low
|
prefix, self.container_low
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-surface-container-high: #{};",
|
"$color-{}-surface-container-high: ${};",
|
||||||
prefix, self.container_high
|
prefix, self.container_high
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-surface-container-highest: #{};",
|
"$color-{}-surface-container-highest: ${};",
|
||||||
prefix, self.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!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-on-surface-variant: #{};",
|
"$color-{}-on-surface-variant: ${};",
|
||||||
prefix, self.on_root_variant
|
prefix, self.on_root_variant
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-inverse-surface: #{};",
|
"$color-{}-inverse-surface: ${};",
|
||||||
prefix, self.inverse
|
prefix, self.inverse
|
||||||
));
|
));
|
||||||
scss_variables.push(format!(
|
scss_variables.push(format!(
|
||||||
"$color-{}-inverse-on-surface: #{};",
|
"$color-{}-inverse-on-surface: ${};",
|
||||||
prefix, self.on_inverse
|
prefix, self.on_inverse
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -222,7 +179,6 @@ impl M3SurfaceSet {
|
||||||
js_object_fields.push(format!("{}Surface: '#{}',", prefix, self.root));
|
js_object_fields.push(format!("{}Surface: '#{}',", prefix, self.root));
|
||||||
js_object_fields.push(format!("{}SurfaceDim: '#{}',", prefix, self.dim));
|
js_object_fields.push(format!("{}SurfaceDim: '#{}',", prefix, self.dim));
|
||||||
js_object_fields.push(format!("{}SurfaceBright: '#{}',", prefix, self.bright));
|
js_object_fields.push(format!("{}SurfaceBright: '#{}',", prefix, self.bright));
|
||||||
js_object_fields.push(format!("{}SurfaceVariant: '#{}',", prefix, self.variant));
|
|
||||||
js_object_fields.push(format!(
|
js_object_fields.push(format!(
|
||||||
"{}SurfaceContainer: '#{}',",
|
"{}SurfaceContainer: '#{}',",
|
||||||
prefix, self.container
|
prefix, self.container
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
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,379 +0,0 @@
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
#[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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,245 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,917 +0,0 @@
|
||||||
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,
|
|
||||||
))
|
|
||||||
}
|
|
|
@ -1,221 +0,0 @@
|
||||||
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,
|
|
||||||
))
|
|
||||||
}
|
|
|
@ -2,7 +2,6 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use material_design_2::MaterialDesign2Scheme;
|
use material_design_2::MaterialDesign2Scheme;
|
||||||
use material_design_3::MaterialDesign3Scheme;
|
use material_design_3::MaterialDesign3Scheme;
|
||||||
use material_design_3_dynamic::{build_baseline, build_dynamic_scheme, build_swatches, Variant};
|
|
||||||
use q_style::{QScheme, SchemeSetting};
|
use q_style::{QScheme, SchemeSetting};
|
||||||
use swatch_style::{SwatchEntry, SwatchSchemeSetting};
|
use swatch_style::{SwatchEntry, SwatchSchemeSetting};
|
||||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||||
|
@ -11,13 +10,11 @@ use crate::errors;
|
||||||
|
|
||||||
pub mod material_design_2;
|
pub mod material_design_2;
|
||||||
pub mod material_design_3;
|
pub mod material_design_3;
|
||||||
pub mod material_design_3_dynamic;
|
|
||||||
pub mod q_style;
|
pub mod q_style;
|
||||||
pub mod swatch_style;
|
pub mod swatch_style;
|
||||||
|
|
||||||
pub trait SchemeExport {
|
pub trait SchemeExport {
|
||||||
fn output_css_variables(&self) -> String;
|
fn output_css_variables(&self) -> String;
|
||||||
fn output_css_auto_scheme_variables(&self) -> String;
|
|
||||||
fn output_scss_variables(&self) -> String;
|
fn output_scss_variables(&self) -> String;
|
||||||
fn output_javascript_object(&self) -> String;
|
fn output_javascript_object(&self) -> String;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +34,6 @@ pub fn generate_material_design_3_scheme(
|
||||||
Ok(serde_wasm_bindgen::to_value(&(
|
Ok(serde_wasm_bindgen::to_value(&(
|
||||||
scheme.clone(),
|
scheme.clone(),
|
||||||
scheme.output_css_variables(),
|
scheme.output_css_variables(),
|
||||||
scheme.output_css_auto_scheme_variables(),
|
|
||||||
scheme.output_scss_variables(),
|
scheme.output_scss_variables(),
|
||||||
scheme.output_javascript_object(),
|
scheme.output_javascript_object(),
|
||||||
))
|
))
|
||||||
|
@ -60,7 +56,6 @@ pub fn generate_material_design_2_scheme(
|
||||||
Ok(serde_wasm_bindgen::to_value(&(
|
Ok(serde_wasm_bindgen::to_value(&(
|
||||||
scheme.clone(),
|
scheme.clone(),
|
||||||
scheme.output_css_variables(),
|
scheme.output_css_variables(),
|
||||||
scheme.output_css_auto_scheme_variables(),
|
|
||||||
scheme.output_scss_variables(),
|
scheme.output_scss_variables(),
|
||||||
scheme.output_javascript_object(),
|
scheme.output_javascript_object(),
|
||||||
))
|
))
|
||||||
|
@ -91,7 +86,6 @@ pub fn generate_q_scheme_automatically(
|
||||||
Ok(serde_wasm_bindgen::to_value(&(
|
Ok(serde_wasm_bindgen::to_value(&(
|
||||||
scheme.clone(),
|
scheme.clone(),
|
||||||
scheme.output_css_variables(),
|
scheme.output_css_variables(),
|
||||||
scheme.output_css_auto_scheme_variables(),
|
|
||||||
scheme.output_scss_variables(),
|
scheme.output_scss_variables(),
|
||||||
scheme.output_javascript_object(),
|
scheme.output_javascript_object(),
|
||||||
))
|
))
|
||||||
|
@ -128,7 +122,6 @@ pub fn generate_q_scheme_manually(
|
||||||
Ok(serde_wasm_bindgen::to_value(&(
|
Ok(serde_wasm_bindgen::to_value(&(
|
||||||
scheme.clone(),
|
scheme.clone(),
|
||||||
scheme.output_css_variables(),
|
scheme.output_css_variables(),
|
||||||
scheme.output_css_auto_scheme_variables(),
|
|
||||||
scheme.output_scss_variables(),
|
scheme.output_scss_variables(),
|
||||||
scheme.output_javascript_object(),
|
scheme.output_javascript_object(),
|
||||||
))
|
))
|
||||||
|
@ -144,55 +137,6 @@ pub fn generate_swatch_scheme(
|
||||||
Ok(serde_wasm_bindgen::to_value(&(
|
Ok(serde_wasm_bindgen::to_value(&(
|
||||||
scheme.swatches(),
|
scheme.swatches(),
|
||||||
scheme.output_css_variables(),
|
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_scss_variables(),
|
||||||
scheme.output_javascript_object(),
|
scheme.output_javascript_object(),
|
||||||
))
|
))
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use palette::{
|
use palette::{
|
||||||
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
|
color_theory::{Analogous, Complementary, SplitComplementary, Tetradic, Triadic},
|
||||||
Oklch, ShiftHue,
|
Oklch, ShiftHue,
|
||||||
|
@ -159,37 +158,6 @@ impl Baseline {
|
||||||
variables
|
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> {
|
pub fn to_scss_variables(&self, prefix: &str) -> Vec<String> {
|
||||||
let mut variables = Vec::new();
|
let mut variables = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use palette::{color_difference::Wcag21RelativeContrast, luma::Luma, Oklch};
|
use palette::{color_difference::Wcag21RelativeContrast, luma::Luma, Oklch};
|
||||||
use serde::{ser::SerializeStruct, Serialize};
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
|
|
||||||
|
@ -180,47 +179,6 @@ impl ColorSet {
|
||||||
variables
|
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> {
|
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||||
let mut variables = Vec::new();
|
let mut variables = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use baseline::Baseline;
|
use baseline::Baseline;
|
||||||
use linked_hash_set::LinkedHashSet;
|
|
||||||
use palette::FromColor;
|
use palette::FromColor;
|
||||||
use scheme_setting::{ColorExpand, WACGSetting};
|
use scheme_setting::{ColorExpand, WACGSetting};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
@ -131,32 +130,6 @@ impl SchemeExport for QScheme {
|
||||||
variables.join("\n")
|
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 {
|
fn output_scss_variables(&self) -> String {
|
||||||
let mut variables = Vec::new();
|
let mut variables = Vec::new();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use linked_hash_set::LinkedHashSet;
|
|
||||||
use palette::FromColor;
|
use palette::FromColor;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -86,56 +84,23 @@ impl SchemeExport for SwatchScheme {
|
||||||
let mut variables = Vec::new();
|
let mut variables = Vec::new();
|
||||||
|
|
||||||
for (name, swatch) in &self.light {
|
for (name, swatch) in &self.light {
|
||||||
variables.extend(swatch.to_css_variables("light", &name.to_lowercase()));
|
variables.extend(swatch.to_css_variables("light", name));
|
||||||
}
|
}
|
||||||
for (name, swatch) in &self.dark {
|
for (name, swatch) in &self.dark {
|
||||||
variables.extend(swatch.to_css_variables("dark", &name.to_lowercase()));
|
variables.extend(swatch.to_css_variables("dark", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
variables.join("\n")
|
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 {
|
fn output_scss_variables(&self) -> String {
|
||||||
let mut variables = Vec::new();
|
let mut variables = Vec::new();
|
||||||
|
|
||||||
for (name, swatch) in &self.light {
|
for (name, swatch) in &self.light {
|
||||||
variables.extend(swatch.to_scss_variables("light", &name.to_lowercase()));
|
variables.extend(swatch.to_scss_variables("light", name));
|
||||||
}
|
}
|
||||||
for (name, swatch) in &self.dark {
|
for (name, swatch) in &self.dark {
|
||||||
variables.extend(swatch.to_scss_variables("dark", &name.to_lowercase()));
|
variables.extend(swatch.to_scss_variables("dark", name));
|
||||||
}
|
}
|
||||||
|
|
||||||
variables.join("\n")
|
variables.join("\n")
|
||||||
|
@ -149,7 +114,7 @@ impl SchemeExport for SwatchScheme {
|
||||||
for (name, swatch) in &self.light {
|
for (name, swatch) in &self.light {
|
||||||
object.extend(
|
object.extend(
|
||||||
swatch
|
swatch
|
||||||
.to_javascript_fields("light", &name.to_lowercase())
|
.to_javascript_fields("light", name)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!(" {}", s)),
|
.map(|s| format!(" {}", s)),
|
||||||
);
|
);
|
||||||
|
@ -160,7 +125,7 @@ impl SchemeExport for SwatchScheme {
|
||||||
for (name, swatch) in &self.dark {
|
for (name, swatch) in &self.dark {
|
||||||
object.extend(
|
object.extend(
|
||||||
swatch
|
swatch
|
||||||
.to_javascript_fields("dark", &name.to_lowercase())
|
.to_javascript_fields("dark", name)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| format!(" {}", s)),
|
.map(|s| format!(" {}", s)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use linked_hash_map::LinkedHashMap;
|
|
||||||
use palette::Oklch;
|
use palette::Oklch;
|
||||||
|
|
||||||
use crate::convert::map_oklch_to_srgb_hex;
|
use crate::convert::map_oklch_to_srgb_hex;
|
||||||
|
@ -95,17 +94,6 @@ impl Swatch {
|
||||||
variables
|
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> {
|
pub fn to_scss_variables(&self, prefix: &str, name: &str) -> Vec<String> {
|
||||||
let mut variables = Vec::new();
|
let mut variables = Vec::new();
|
||||||
for (i, color) in self.swatch().iter().enumerate() {
|
for (i, color) in self.swatch().iter().enumerate() {
|
||||||
|
|
|
@ -106,33 +106,3 @@ pub fn triadic(color: &str) -> Result<Vec<String>, errors::ColorError> {
|
||||||
format!("{:x}", srgb_p240.into_format::<u8>()),
|
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))
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description"
|
<meta name="description"
|
||||||
content="By transforming and selecting various color theories, freely design UI color combinations." />
|
content="By transforming and selecting various color theories, freely design UI color combinations." />
|
||||||
|
|
6
logo.svg
6
logo.svg
|
@ -1,6 +0,0 @@
|
||||||
<?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>
|
|
Before Width: | Height: | Size: 289 B |
|
@ -42,6 +42,7 @@ export function ColorFunctionProvider({ children }: WasmProviderProps) {
|
||||||
try {
|
try {
|
||||||
await init();
|
await init();
|
||||||
setWasmInstance(funcs);
|
setWasmInstance(funcs);
|
||||||
|
console.debug('[Load WASM]', 'Loaded');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Load WASM]', e);
|
console.error('[Load WASM]', e);
|
||||||
setError(e);
|
setError(e);
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
import { Icon, IconProps } from '@iconify/react/dist/iconify.js';
|
import { Icon, IconProps } from '@iconify/react/dist/iconify.js';
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { MouseEvent, MouseEventHandler, RefObject, useCallback } from 'react';
|
import { MouseEvent, MouseEventHandler, useCallback } from 'react';
|
||||||
import styles from './ActionIcon.module.css';
|
import styles from './ActionIcon.module.css';
|
||||||
|
|
||||||
type ActionIconProps = {
|
type ActionIconProps = {
|
||||||
icon: IconProps['icon'];
|
icon: IconProps['icon'];
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>;
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
||||||
extendClassName?: HTMLButtonElement['className'];
|
extendClassName?: HTMLButtonElement['className'];
|
||||||
ref?: RefObject<HTMLButtonElement>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ActionIcon({ icon, onClick, extendClassName, ref }: ActionIconProps) {
|
export function ActionIcon({ icon, onClick, extendClassName }: ActionIconProps) {
|
||||||
const handleClick = useCallback(
|
const handleClick = useCallback(
|
||||||
(event: MouseEvent<HTMLButtonElement>) => {
|
(event: MouseEvent<HTMLButtonElement>) => {
|
||||||
onClick?.(event);
|
onClick?.(event);
|
||||||
|
@ -19,11 +18,7 @@ export function ActionIcon({ icon, onClick, extendClassName, ref }: ActionIconPr
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button type="button" onClick={handleClick} className={cx(styles.action_icon, extendClassName)}>
|
||||||
type="button"
|
|
||||||
onClick={handleClick}
|
|
||||||
className={cx(styles.action_icon, extendClassName)}
|
|
||||||
ref={ref}>
|
|
||||||
<Icon icon={icon} className={styles.icon} />
|
<Icon icon={icon} className={styles.icon} />
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,390 +0,0 @@
|
||||||
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 { 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 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 '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;
|
|
|
@ -11,19 +11,12 @@
|
||||||
.color_block {
|
.color_block {
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
}
|
}
|
||||||
.operate_row {
|
.color_value {
|
||||||
padding: var(--spacing-xxs) var(--spacing-xs);
|
padding: var(--spacing-xxs) var(--spacing-xs);
|
||||||
font-size: var(--font-size-xs);
|
font-size: var(--font-size-xs);
|
||||||
display: flex;
|
text-align: right;
|
||||||
flex-direction: row;
|
text-transform: uppercase;
|
||||||
justify-content: flex-end;
|
cursor: pointer;
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
.color_value {
|
|
||||||
text-align: right;
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useColorFunction } from '../ColorFunctionContext';
|
import { useColorFunction } from '../ColorFunctionContext';
|
||||||
import { useCopyColor } from '../hooks/useCopyColor';
|
import { useCopyColor } from '../hooks/useCopyColor';
|
||||||
import ContextMenu from './ContextMenu';
|
|
||||||
import styles from './FlexColorStand.module.css';
|
import styles from './FlexColorStand.module.css';
|
||||||
|
|
||||||
type FlexColorStandProps = {
|
type FlexColorStandProps = {
|
||||||
|
@ -52,11 +51,8 @@ export function FlexColorStand({ color, valueMode = 'hex' }: FlexColorStandProps
|
||||||
return (
|
return (
|
||||||
<div className={styles.color_stand}>
|
<div className={styles.color_stand}>
|
||||||
<div className={styles.color_block} style={{ backgroundColor: bgColor }} />
|
<div className={styles.color_block} style={{ backgroundColor: bgColor }} />
|
||||||
<div className={styles.operate_row}>
|
<div className={styles.color_value} onClick={() => copyToClipboard(colorValue)}>
|
||||||
<div className={styles.color_value} onClick={() => copyToClipboard(colorValue)}>
|
{bgColor}
|
||||||
{bgColor}
|
|
||||||
</div>
|
|
||||||
<ContextMenu color={color} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,9 +17,5 @@
|
||||||
background-color: #a78fff;
|
background-color: #a78fff;
|
||||||
color: var(--color-qihei);
|
color: var(--color-qihei);
|
||||||
}
|
}
|
||||||
&.m3d {
|
|
||||||
background-color: #ffde3f;
|
|
||||||
color: var(--color-qihei);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,6 @@ export function SchemeSign({ scheme, short = false }: SchemeSignProps) {
|
||||||
return styles.m2;
|
return styles.m2;
|
||||||
case 'material_3':
|
case 'material_3':
|
||||||
return styles.m3;
|
return styles.m3;
|
||||||
case 'material_3_dynamic':
|
|
||||||
return styles.m3d;
|
|
||||||
}
|
}
|
||||||
}, [scheme]);
|
}, [scheme]);
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,9 @@ export function Switch({ name, checked = false, disabled = false, onChange }: Sw
|
||||||
<div
|
<div
|
||||||
className={cx(styles.switch_handle, isChecked && styles.checked)}
|
className={cx(styles.switch_handle, isChecked && styles.checked)}
|
||||||
onClick={handleSwitch}></div>
|
onClick={handleSwitch}></div>
|
||||||
{!isNil(name) && <input type="hidden" name={name} value={isChecked ? 'true' : 'false'} />}
|
{!isNil(name) && (
|
||||||
|
<input type="hidden" name={name} value={isChecked ? 'checked' : undefined} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,5 @@
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-primary-hover);
|
color: var(--color-primary-hover);
|
||||||
}
|
}
|
||||||
&.disabled {
|
|
||||||
color: var(--color-primary-disabled);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,30 +3,20 @@ import { isEqual, isNil } from 'lodash-es';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import styles from './Tab.module.css';
|
import styles from './Tab.module.css';
|
||||||
|
|
||||||
type TabOption = {
|
|
||||||
title: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TabProps = {
|
type TabProps = {
|
||||||
tabs: TabOption[];
|
tabs: { title: string; id: unknown }[];
|
||||||
activeTab?: unknown;
|
activeTab?: unknown;
|
||||||
onActive?: (id: TabOption['id']) => void;
|
onActive?: (id: unknown) => void;
|
||||||
disabled?: Record<TabOption['id'], boolean>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Tab({ tabs = [], activeTab, onActive, disabled }: TabProps) {
|
export function Tab({ tabs = [], activeTab, onActive }: TabProps) {
|
||||||
const [active, setActive] = useState(() =>
|
const [active, setActive] = useState(() =>
|
||||||
isNil(activeTab) ? 0 : tabs.findIndex((tab) => isEqual(tab.id, activeTab)),
|
isNil(activeTab) ? 0 : tabs.findIndex((tab) => isEqual(tab.id, activeTab)),
|
||||||
);
|
);
|
||||||
const handleActivate = useCallback(
|
const handleActivate = useCallback((index: number) => {
|
||||||
(index: number) => {
|
setActive(index);
|
||||||
if (disabled?.[tabs[index].id] ?? false) return;
|
onActive?.(tabs[index].id);
|
||||||
setActive(index);
|
}, []);
|
||||||
onActive?.(tabs[index].id);
|
|
||||||
},
|
|
||||||
[tabs, onActive, disabled],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const activeIndex = tabs.findIndex((tab) => isEqual(tab.id, activeTab));
|
const activeIndex = tabs.findIndex((tab) => isEqual(tab.id, activeTab));
|
||||||
|
@ -40,11 +30,7 @@ export function Tab({ tabs = [], activeTab, onActive, disabled }: TabProps) {
|
||||||
{tabs.map((tab, index) => (
|
{tabs.map((tab, index) => (
|
||||||
<div
|
<div
|
||||||
key={`tab_${index}_${tab.id}`}
|
key={`tab_${index}_${tab.id}`}
|
||||||
className={cx(
|
className={cx(styles.tab, isEqual(index, active) && styles.actived)}
|
||||||
styles.tab,
|
|
||||||
isEqual(index, active) && styles.actived,
|
|
||||||
(disabled?.[tab.id] ?? false) && styles.disabled,
|
|
||||||
)}
|
|
||||||
onClick={() => handleActivate(index)}>
|
onClick={() => handleActivate(index)}>
|
||||||
{tab.title}
|
{tab.title}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -32,7 +32,6 @@ export type MaterialDesign2SchemeStorage = {
|
||||||
source?: MaterialDesign2SchemeSource;
|
source?: MaterialDesign2SchemeSource;
|
||||||
scheme?: MaterialDesign2Scheme;
|
scheme?: MaterialDesign2Scheme;
|
||||||
cssVariables?: string;
|
cssVariables?: string;
|
||||||
cssAutoSchemeVariables?: string;
|
|
||||||
scssVariables?: string;
|
scssVariables?: string;
|
||||||
jsVariables?: string;
|
jsVariables?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -55,25 +55,6 @@ export type MaterialDesign3SchemeStorage = {
|
||||||
source?: MaterialDesign3SchemeSource;
|
source?: MaterialDesign3SchemeSource;
|
||||||
scheme?: MaterialDesign3Scheme;
|
scheme?: MaterialDesign3Scheme;
|
||||||
cssVariables?: string;
|
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;
|
scssVariables?: string;
|
||||||
jsVariables?: string;
|
jsVariables?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import { find, isNil } from 'lodash-es';
|
import { find, isNil } from 'lodash-es';
|
||||||
import { MaterialDesign2SchemeStorage } from './material-2-scheme';
|
import { MaterialDesign2SchemeStorage } from './material-2-scheme';
|
||||||
import {
|
import { MaterialDesign3SchemeStorage } from './material-3-scheme';
|
||||||
MaterialDesign3DynamicSchemeStorage,
|
|
||||||
MaterialDesign3SchemeStorage,
|
|
||||||
} from './material-3-scheme';
|
|
||||||
import { QSchemeStorage } from './q-scheme';
|
import { QSchemeStorage } from './q-scheme';
|
||||||
import { SwatchSchemeStorage } from './swatch_scheme';
|
import { SwatchSchemeStorage } from './swatch_scheme';
|
||||||
|
|
||||||
|
@ -32,12 +29,7 @@ export type ColorDescription = {
|
||||||
oklch: [number, number, number];
|
oklch: [number, number, number];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SchemeType =
|
export type SchemeType = 'q_scheme' | 'swatch_scheme' | 'material_2' | 'material_3';
|
||||||
| 'q_scheme'
|
|
||||||
| 'swatch_scheme'
|
|
||||||
| 'material_2'
|
|
||||||
| 'material_3'
|
|
||||||
| 'material_3_dynamic';
|
|
||||||
export type SchemeTypeOption = {
|
export type SchemeTypeOption = {
|
||||||
label: string;
|
label: string;
|
||||||
short: string;
|
short: string;
|
||||||
|
@ -48,7 +40,6 @@ export const SchemeTypeOptions: SchemeTypeOption[] = [
|
||||||
{ label: 'Swatch Scheme', short: 'Swatch', value: 'swatch_scheme' },
|
{ label: 'Swatch Scheme', short: 'Swatch', value: 'swatch_scheme' },
|
||||||
{ label: 'Material Design 2 Scheme', short: 'M2', value: 'material_2' },
|
{ label: 'Material Design 2 Scheme', short: 'M2', value: 'material_2' },
|
||||||
{ label: 'Material Design 3 Scheme', short: 'M3', value: 'material_3' },
|
{ 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(
|
export function schemeType(
|
||||||
|
@ -81,5 +72,4 @@ export type SchemeStorage =
|
||||||
| QSchemeStorage
|
| QSchemeStorage
|
||||||
| SwatchSchemeStorage
|
| SwatchSchemeStorage
|
||||||
| MaterialDesign2SchemeStorage
|
| MaterialDesign2SchemeStorage
|
||||||
| MaterialDesign3SchemeStorage
|
| MaterialDesign3SchemeStorage;
|
||||||
| MaterialDesign3DynamicSchemeStorage;
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
line-height: var(--font-size-xxs);
|
line-height: var(--font-size-xxs);
|
||||||
border: 1px solid var(--color-border);
|
border: 1px solid var(--color-border);
|
||||||
border-radius: var(--border-radius-xs);
|
border-radius: var(--border-radius-xs);
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.color_block {
|
.color_block {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -18,7 +19,6 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: var(--spacing-xs) var(--spacing-s);
|
padding: var(--spacing-xs) var(--spacing-s);
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
}
|
||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -38,6 +38,5 @@
|
||||||
}
|
}
|
||||||
.color_value {
|
.color_value {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { capitalize, isEmpty } from 'lodash-es';
|
import { capitalize, isEmpty } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { useColorFunction } from '../../ColorFunctionContext';
|
import { useColorFunction } from '../../ColorFunctionContext';
|
||||||
import ContextMenu from '../../components/ContextMenu';
|
|
||||||
import { useCopyColor } from '../../hooks/useCopyColor';
|
import { useCopyColor } from '../../hooks/useCopyColor';
|
||||||
import { ColorDescription } from '../../models';
|
import { ColorDescription } from '../../models';
|
||||||
import styles from './ColorCard.module.css';
|
import styles from './ColorCard.module.css';
|
||||||
|
@ -58,7 +57,7 @@ export function ColorCard({ color, copyMode }: ColorCardProps) {
|
||||||
}, [copytToClipboard, color, copyMode, colorHex]);
|
}, [copytToClipboard, color, copyMode, colorHex]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.card}>
|
<div className={styles.card} onClick={handleCopy}>
|
||||||
<div
|
<div
|
||||||
className={styles.color_block}
|
className={styles.color_block}
|
||||||
style={{ backgroundColor: `rgb(${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})` }}
|
style={{ backgroundColor: `rgb(${color.rgb[0]}, ${color.rgb[1]}, ${color.rgb[2]})` }}
|
||||||
|
@ -70,10 +69,7 @@ export function ColorCard({ color, copyMode }: ColorCardProps) {
|
||||||
<span className={styles.en_name}>{color.pinyin.map(capitalize).join(' ')}</span>
|
<span className={styles.en_name}>{color.pinyin.map(capitalize).join(' ')}</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.color_value} onClick={handleCopy}>
|
<div className={styles.color_value}>#{colorHex}</div>
|
||||||
#{colorHex}
|
|
||||||
</div>
|
|
||||||
<ContextMenu color={colorHex} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
@layer pages {
|
@layer pages {
|
||||||
.preview {
|
.preview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
padding: 0 var(--spacing-m);
|
padding: 0 var(--spacing-m);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -10,7 +9,6 @@
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
}
|
}
|
||||||
.color_blocks {
|
.color_blocks {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
@ -18,7 +16,6 @@
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
.color_block {
|
.color_block {
|
||||||
max-height: 23em;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
@ -45,25 +42,14 @@
|
||||||
padding-inline: var(--spacing-s);
|
padding-inline: var(--spacing-s);
|
||||||
font-size: var(--font-size-s);
|
font-size: var(--font-size-s);
|
||||||
}
|
}
|
||||||
.color_code_row {
|
.color_code {
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
padding-inline: var(--spacing-s);
|
padding-inline: var(--spacing-s);
|
||||||
font-size: var(--font-size-s);
|
font-size: var(--font-size-s);
|
||||||
display: flex;
|
text-transform: uppercase;
|
||||||
flex-direction: row;
|
text-align: right;
|
||||||
justify-content: flex-end;
|
> span {
|
||||||
align-items: center;
|
cursor: pointer;
|
||||||
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,7 +1,6 @@
|
||||||
import cx from 'clsx';
|
import cx from 'clsx';
|
||||||
import { constant, flatten, isEqual, take, times } from 'lodash-es';
|
import { constant, flatten, isEqual, take, times } from 'lodash-es';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import ContextMenu from '../../components/ContextMenu';
|
|
||||||
import { useCopyColor } from '../../hooks/useCopyColor';
|
import { useCopyColor } from '../../hooks/useCopyColor';
|
||||||
import { HarmonyColor } from '../../models';
|
import { HarmonyColor } from '../../models';
|
||||||
import styles from './HarmonyPreview.module.css';
|
import styles from './HarmonyPreview.module.css';
|
||||||
|
@ -34,11 +33,8 @@ export function HarmonyPreview({ colors = [] }: HarmonyPreviewProps) {
|
||||||
style={{ flexGrow: ratio }}>
|
style={{ flexGrow: ratio }}>
|
||||||
<div className={styles.color_ratio}>{ratio > 0 && `Ratio: ${ratio}`}</div>
|
<div className={styles.color_ratio}>{ratio > 0 && `Ratio: ${ratio}`}</div>
|
||||||
<div className={styles.color_square} style={{ backgroundColor: `#${color}` }}></div>
|
<div className={styles.color_square} style={{ backgroundColor: `#${color}` }}></div>
|
||||||
<div className={styles.color_code_row}>
|
<div className={styles.color_code}>
|
||||||
<div className={styles.color_code}>
|
{ratio > 0 && <span onClick={() => copyColor(color)}>#{color}</span>}
|
||||||
{ratio > 0 && <span onClick={() => copyColor(color)}>#{color}</span>}
|
|
||||||
</div>
|
|
||||||
<ContextMenu color={color} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import styles from './Export.module.css';
|
||||||
|
|
||||||
const exportOptions: Option[] = [
|
const exportOptions: Option[] = [
|
||||||
{ label: 'CSS', value: 'css' },
|
{ label: 'CSS', value: 'css' },
|
||||||
{ label: 'CSS Auto Scheme', value: 'css-auto' },
|
|
||||||
{ label: 'SCSS', value: 'scss' },
|
{ label: 'SCSS', value: 'scss' },
|
||||||
{ label: 'Javascript Object', value: 'js_object' },
|
{ label: 'Javascript Object', value: 'js_object' },
|
||||||
];
|
];
|
||||||
|
@ -23,8 +22,6 @@ export function SchemeExport({ scheme }: SchemeExportProps) {
|
||||||
switch (activeExport) {
|
switch (activeExport) {
|
||||||
case 'css':
|
case 'css':
|
||||||
return scheme.schemeStorage.cssVariables;
|
return scheme.schemeStorage.cssVariables;
|
||||||
case 'css-auto':
|
|
||||||
return scheme.schemeStorage.cssAutoSchemeVariables;
|
|
||||||
case 'scss':
|
case 'scss':
|
||||||
return scheme.schemeStorage.scssVariables;
|
return scheme.schemeStorage.scssVariables;
|
||||||
case 'js_object':
|
case 'js_object':
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useState } from 'react';
|
||||||
import { Tab } from '../../components/Tab';
|
import { Tab } from '../../components/Tab';
|
||||||
import { MaterialDesign2SchemeStorage } from '../../material-2-scheme';
|
import { MaterialDesign2SchemeStorage } from '../../material-2-scheme';
|
||||||
import { SchemeContent } from '../../models';
|
import { SchemeContent } from '../../models';
|
||||||
import { isNilOrEmpty } from '../../utls';
|
|
||||||
import { SchemeExport } from './Export';
|
import { SchemeExport } from './Export';
|
||||||
import { M2SchemeBuilder } from './m2-scheme/Builder';
|
import { M2SchemeBuilder } from './m2-scheme/Builder';
|
||||||
import { M2SchemePreview } from './m2-scheme/Preview';
|
import { M2SchemePreview } from './m2-scheme/Preview';
|
||||||
|
@ -25,15 +24,7 @@ export function M2Scheme({ scheme }: M2SchemeProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tab
|
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
|
||||||
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, 'overview') && <M2SchemePreview scheme={scheme} />}
|
||||||
{isEqual(activeTab, 'builder') && (
|
{isEqual(activeTab, 'builder') && (
|
||||||
<M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />
|
<M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
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} />}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ import { useState } from 'react';
|
||||||
import { Tab } from '../../components/Tab';
|
import { Tab } from '../../components/Tab';
|
||||||
import { MaterialDesign3SchemeStorage } from '../../material-3-scheme';
|
import { MaterialDesign3SchemeStorage } from '../../material-3-scheme';
|
||||||
import { SchemeContent } from '../../models';
|
import { SchemeContent } from '../../models';
|
||||||
import { isNilOrEmpty } from '../../utls';
|
|
||||||
import { SchemeExport } from './Export';
|
import { SchemeExport } from './Export';
|
||||||
import { M3SchemeBuilder } from './m3-scheme/Builder';
|
import { M3SchemeBuilder } from './m3-scheme/Builder';
|
||||||
import { M3SchemePreview } from './m3-scheme/Preview';
|
import { M3SchemePreview } from './m3-scheme/Preview';
|
||||||
|
@ -25,16 +24,8 @@ export function M3Scheme({ scheme }: M3SchemeProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tab
|
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
|
||||||
tabs={tabOptions}
|
{isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme} />}
|
||||||
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') && (
|
{isEqual(activeTab, 'builder') && (
|
||||||
<M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
<M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useState } from 'react';
|
||||||
import { Tab } from '../../components/Tab';
|
import { Tab } from '../../components/Tab';
|
||||||
import { SchemeContent } from '../../models';
|
import { SchemeContent } from '../../models';
|
||||||
import { QSchemeStorage } from '../../q-scheme';
|
import { QSchemeStorage } from '../../q-scheme';
|
||||||
import { isNilOrEmpty } from '../../utls';
|
|
||||||
import { SchemeExport } from './Export';
|
import { SchemeExport } from './Export';
|
||||||
import { QSchemeBuilder } from './q-scheme/Builder';
|
import { QSchemeBuilder } from './q-scheme/Builder';
|
||||||
import { QSchemePreview } from './q-scheme/Preview';
|
import { QSchemePreview } from './q-scheme/Preview';
|
||||||
|
@ -25,15 +24,7 @@ export function QScheme({ scheme }: QSchemeProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tab
|
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
|
||||||
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, 'overview') && <QSchemePreview scheme={scheme} />}
|
||||||
{isEqual(activeTab, 'builder') && (
|
{isEqual(activeTab, 'builder') && (
|
||||||
<QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
<QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useState } from 'react';
|
||||||
import { Tab } from '../../components/Tab';
|
import { Tab } from '../../components/Tab';
|
||||||
import { SchemeContent } from '../../models';
|
import { SchemeContent } from '../../models';
|
||||||
import { SwatchSchemeStorage } from '../../swatch_scheme';
|
import { SwatchSchemeStorage } from '../../swatch_scheme';
|
||||||
import { isNilOrEmpty } from '../../utls';
|
|
||||||
import { SchemeExport } from './Export';
|
import { SchemeExport } from './Export';
|
||||||
import { SwatchSchemeBuilder } from './swatch-scheme/Builder';
|
import { SwatchSchemeBuilder } from './swatch-scheme/Builder';
|
||||||
import { SwatchSchemePreview } from './swatch-scheme/Preview';
|
import { SwatchSchemePreview } from './swatch-scheme/Preview';
|
||||||
|
@ -25,15 +24,7 @@ export function SwatchScheme({ scheme }: SwatchSchemeProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Tab
|
<Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
|
||||||
tabs={tabOptions}
|
|
||||||
activeTab={activeTab}
|
|
||||||
onActive={(v) => setActiveTab(v as string)}
|
|
||||||
disabled={{
|
|
||||||
overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
|
|
||||||
export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{isEqual(activeTab, 'overview') && <SwatchSchemePreview scheme={scheme} />}
|
{isEqual(activeTab, 'overview') && <SwatchSchemePreview scheme={scheme} />}
|
||||||
{isEqual(activeTab, 'builder') && (
|
{isEqual(activeTab, 'builder') && (
|
||||||
<SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
<SwatchSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
|
||||||
|
|
|
@ -23,12 +23,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
}
|
}
|
||||||
.button_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
line-height: 1.7em;
|
line-height: 1.7em;
|
||||||
|
|
|
@ -2,12 +2,8 @@ import { includes, isEmpty, isNil, merge } from 'lodash-es';
|
||||||
import { useActionState, useCallback, useMemo, useState } from 'react';
|
import { useActionState, useCallback, useMemo, useState } from 'react';
|
||||||
import { useColorFunction } from '../../../ColorFunctionContext';
|
import { useColorFunction } from '../../../ColorFunctionContext';
|
||||||
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
||||||
import { NotificationType, useNotification } from '../../../components/Notifications';
|
|
||||||
import { ScrollArea } from '../../../components/ScrollArea';
|
import { ScrollArea } from '../../../components/ScrollArea';
|
||||||
import {
|
import { MaterialDesign2SchemeStorage } from '../../../material-2-scheme';
|
||||||
MaterialDesign2SchemeSource,
|
|
||||||
MaterialDesign2SchemeStorage,
|
|
||||||
} from '../../../material-2-scheme';
|
|
||||||
import { SchemeContent } from '../../../models';
|
import { SchemeContent } from '../../../models';
|
||||||
import { useUpdateScheme } from '../../../stores/schemes';
|
import { useUpdateScheme } from '../../../stores/schemes';
|
||||||
import { mapToObject } from '../../../utls';
|
import { mapToObject } from '../../../utls';
|
||||||
|
@ -20,7 +16,6 @@ type M2SchemeBuilderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProps) {
|
export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProps) {
|
||||||
const { showToast } = useNotification();
|
|
||||||
const { colorFn } = useColorFunction();
|
const { colorFn } = useColorFunction();
|
||||||
const updateScheme = useUpdateScheme(scheme.id);
|
const updateScheme = useUpdateScheme(scheme.id);
|
||||||
const originalColors = useMemo(() => {
|
const originalColors = useMemo(() => {
|
||||||
|
@ -40,75 +35,52 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
|
||||||
.filter((c) => !includes(deleted, c)),
|
.filter((c) => !includes(deleted, c)),
|
||||||
[originalColors, newColors, deleted],
|
[originalColors, newColors, deleted],
|
||||||
);
|
);
|
||||||
const colectSchemeSource = (formData: FormData): MaterialDesign2SchemeSource => {
|
|
||||||
const primaryColor = formData.get('primary') as string;
|
|
||||||
const secondaryColor = formData.get('secondary') as string;
|
|
||||||
const errorColor = formData.get('error') as string;
|
|
||||||
const customColors: Record<string, string> = {};
|
|
||||||
for (const key of colorKeys) {
|
|
||||||
const name = formData.get(`name_${key}`) as string;
|
|
||||||
const color = formData.get(`color_${key}`) as string;
|
|
||||||
if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
|
|
||||||
customColors[name] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
primary: isNil(primaryColor) || isEmpty(primaryColor) ? null : primaryColor,
|
|
||||||
secondary: isNil(secondaryColor) || isEmpty(secondaryColor) ? null : secondaryColor,
|
|
||||||
error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
|
|
||||||
custom_colors: customColors,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
|
|
||||||
(_state, formData) => {
|
|
||||||
const errMsg = new Map<string, string>();
|
|
||||||
|
|
||||||
const collectedSource = colectSchemeSource(formData);
|
|
||||||
updateScheme((prev) => {
|
|
||||||
prev.schemeStorage.source = collectedSource;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
setNewColors([]);
|
|
||||||
|
|
||||||
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
},
|
|
||||||
new Map<string, string>(),
|
|
||||||
);
|
|
||||||
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
||||||
(_state, formData) => {
|
(_state, formData) => {
|
||||||
const errMsg = new Map<string, string>();
|
const errMsg = new Map<string, string>();
|
||||||
try {
|
try {
|
||||||
const collected = colectSchemeSource(formData);
|
const primaryColor = formData.get('primary') as string;
|
||||||
if (isNil(collected.primary) || isEmpty(collected.primary)) {
|
if (isNil(primaryColor) || isEmpty(primaryColor)) {
|
||||||
errMsg.set('primary', 'Primary color is required');
|
errMsg.set('primary', 'Primary color is required');
|
||||||
}
|
}
|
||||||
if (isNil(collected.secondary) || isEmpty(collected.secondary)) {
|
const secondaryColor = formData.get('secondary') as string;
|
||||||
|
if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
|
||||||
errMsg.set('secondary', 'Secondary color is required');
|
errMsg.set('secondary', 'Secondary color is required');
|
||||||
}
|
}
|
||||||
if (isNil(collected.error) || isEmpty(collected.error)) {
|
const errorColor = formData.get('error') as string;
|
||||||
|
if (isNil(errorColor) || isEmpty(errorColor)) {
|
||||||
errMsg.set('error', 'Error color is required');
|
errMsg.set('error', 'Error color is required');
|
||||||
}
|
}
|
||||||
if (!isEmpty(errMsg)) return errMsg;
|
if (!isEmpty(errMsg)) return errMsg;
|
||||||
|
|
||||||
|
const customColors: Record<string, string> = {};
|
||||||
|
for (const key of colorKeys) {
|
||||||
|
const name = formData.get(`name_${key}`) as string;
|
||||||
|
const color = formData.get(`color_${key}`) as string;
|
||||||
|
if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
|
||||||
|
customColors[name] = color;
|
||||||
|
}
|
||||||
const generatedScheme = colorFn?.generate_material_design_2_scheme(
|
const generatedScheme = colorFn?.generate_material_design_2_scheme(
|
||||||
collected.primary,
|
primaryColor,
|
||||||
collected.secondary,
|
secondaryColor,
|
||||||
collected.error,
|
errorColor,
|
||||||
collected.custom_colors,
|
customColors,
|
||||||
);
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = collected;
|
prev.schemeStorage.source = {
|
||||||
|
primary: primaryColor,
|
||||||
|
secondary: secondaryColor,
|
||||||
|
error: errorColor,
|
||||||
|
custom_colors: customColors,
|
||||||
|
};
|
||||||
prev.schemeStorage.scheme = merge(generatedScheme[0], {
|
prev.schemeStorage.scheme = merge(generatedScheme[0], {
|
||||||
light: { custom_colors: mapToObject(generatedScheme[0].light.custom_colors) },
|
light: { custom_colors: mapToObject(generatedScheme[0].light.custom_colors) },
|
||||||
dark: { custom_colors: mapToObject(generatedScheme[0].dark.custom_colors) },
|
dark: { custom_colors: mapToObject(generatedScheme[0].dark.custom_colors) },
|
||||||
});
|
});
|
||||||
prev.schemeStorage.cssVariables = generatedScheme[1];
|
prev.schemeStorage.cssVariables = generatedScheme[1];
|
||||||
prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
|
prev.schemeStorage.scssVariables = generatedScheme[2];
|
||||||
prev.schemeStorage.scssVariables = generatedScheme[3];
|
prev.schemeStorage.jsVariables = generatedScheme[3];
|
||||||
prev.schemeStorage.jsVariables = generatedScheme[4];
|
|
||||||
return prev;
|
return prev;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -195,13 +167,10 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
<button type="submit" className="primary">
|
<button type="submit" className="primary">
|
||||||
Build Scheme
|
Build Scheme
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="secondary" formAction={handleDraftAction}>
|
|
||||||
Save Draft
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
@layer pages {
|
|
||||||
.builder_layout {
|
|
||||||
padding: var(--spacing-s) var(--spacing-m);
|
|
||||||
font-size: var(--font-size-s);
|
|
||||||
line-height: 1.3em;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 200px);
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-xs);
|
|
||||||
.label {
|
|
||||||
max-width: 200px;
|
|
||||||
grid-column: 1;
|
|
||||||
padding-inline-end: var(--spacing-m);
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.segment_title {
|
|
||||||
grid-column: 1 / span 2;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.color_picker_row {
|
|
||||||
grid-column: 2 / span 2;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
.parallel_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
.button_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
h5 {
|
|
||||||
font-size: var(--font-size-m);
|
|
||||||
line-height: 1.7em;
|
|
||||||
}
|
|
||||||
.error_msg {
|
|
||||||
color: var(--color-danger);
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
}
|
|
||||||
.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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,245 +0,0 @@
|
||||||
import { includes, isEmpty, isEqual, isNil } from 'lodash-es';
|
|
||||||
import { useActionState, useCallback, useMemo, useState } from 'react';
|
|
||||||
import { useColorFunction } from '../../../ColorFunctionContext';
|
|
||||||
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
|
||||||
import { NotificationType, useNotification } from '../../../components/Notifications';
|
|
||||||
import { ScrollArea } from '../../../components/ScrollArea';
|
|
||||||
import { Switch } from '../../../components/Switch';
|
|
||||||
import { VSegmentedControl } from '../../../components/VSegmentedControl';
|
|
||||||
import {
|
|
||||||
MaterialDesign3DynamicSchemeSource,
|
|
||||||
MaterialDesign3DynamicSchemeStorage,
|
|
||||||
} from '../../../material-3-scheme';
|
|
||||||
import { Option, SchemeContent } from '../../../models';
|
|
||||||
import { useUpdateScheme } from '../../../stores/schemes';
|
|
||||||
import { mapToObject } from '../../../utls';
|
|
||||||
import { ColorEntry, IdenticalColorEntry } from '../ColorEntry';
|
|
||||||
import styles from './Builder.module.css';
|
|
||||||
|
|
||||||
type M3DynamicSchemeBuilderProps = {
|
|
||||||
scheme: SchemeContent<MaterialDesign3DynamicSchemeStorage>;
|
|
||||||
onBuildCompleted?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function M3DynamicSchemeBuilder({ scheme, onBuildCompleted }: M3DynamicSchemeBuilderProps) {
|
|
||||||
const { showToast } = useNotification();
|
|
||||||
const { colorFn } = useColorFunction();
|
|
||||||
const updateScheme = useUpdateScheme(scheme.id);
|
|
||||||
const originalColors = useMemo(() => {
|
|
||||||
return Object.entries(scheme.schemeStorage.source?.custom_colors ?? {}).map(
|
|
||||||
([name, color], index) => ({ id: `oc_${index}`, name, color } as IdenticalColorEntry),
|
|
||||||
);
|
|
||||||
}, [scheme.schemeStorage.source]);
|
|
||||||
const [newColors, setNewColors] = useState<IdenticalColorEntry[]>([]);
|
|
||||||
const [deleted, setDeleted] = useState<string[]>([]);
|
|
||||||
const addEntryAction = useCallback(() => {
|
|
||||||
setNewColors((prev) => [...prev, { id: `nc_${prev.length}`, name: '', color: '' }]);
|
|
||||||
}, []);
|
|
||||||
const colorKeys = useMemo(
|
|
||||||
() =>
|
|
||||||
[...originalColors, ...newColors]
|
|
||||||
.map((color) => color.id)
|
|
||||||
.filter((c) => !includes(deleted, c)),
|
|
||||||
[originalColors, newColors, deleted],
|
|
||||||
);
|
|
||||||
const variantOptions = useMemo(() => {
|
|
||||||
if (!colorFn) return [];
|
|
||||||
try {
|
|
||||||
return colorFn.material_design_3_dynamic_variant() as Option[];
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[m3 dynamic builder]', e);
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}, []);
|
|
||||||
const [contrastLevel, setContrastLevel] = useState<number>(
|
|
||||||
() => scheme.schemeStorage.source?.contrastLevel ?? 1,
|
|
||||||
);
|
|
||||||
const collectSchemeSource = (formData: FormData): MaterialDesign3DynamicSchemeSource => {
|
|
||||||
const sourceColor = formData.get('source') as string;
|
|
||||||
const dynamicVariant = Number(formData.get('variant'));
|
|
||||||
const contrast = Number(formData.get('contrast_level'));
|
|
||||||
const harmonizeCustoms = isEqual(formData.get('harmonize_customs'), 'true');
|
|
||||||
const errorColor = formData.get('error') as string;
|
|
||||||
const customColors: Record<string, string> = {};
|
|
||||||
for (const key of colorKeys) {
|
|
||||||
const name = formData.get(`name_${key}`) as string;
|
|
||||||
const color = formData.get(`color_${key}`) as string;
|
|
||||||
if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
|
|
||||||
customColors[name] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
source: isNil(sourceColor) || isEmpty(sourceColor) ? null : sourceColor,
|
|
||||||
error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
|
|
||||||
custom_colors: customColors,
|
|
||||||
variant: dynamicVariant,
|
|
||||||
contrastLevel: contrast,
|
|
||||||
harmonizeCustoms: harmonizeCustoms,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
|
|
||||||
(_state, formData) => {
|
|
||||||
const errMsg = new Map<string, string>();
|
|
||||||
|
|
||||||
const collectedSource = collectSchemeSource(formData);
|
|
||||||
|
|
||||||
updateScheme((prev) => {
|
|
||||||
prev.schemeStorage.source = collectedSource;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
setNewColors([]);
|
|
||||||
|
|
||||||
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
},
|
|
||||||
new Map<string, string>(),
|
|
||||||
);
|
|
||||||
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
|
||||||
(_state, formData) => {
|
|
||||||
const errMsg = new Map<string, string>();
|
|
||||||
|
|
||||||
const collectedSource = collectSchemeSource(formData);
|
|
||||||
if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) {
|
|
||||||
errMsg.set('source', 'Source color is required');
|
|
||||||
}
|
|
||||||
if (!isEmpty(errMsg)) return errMsg;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const generate_scheme = colorFn.generate_material_design_3_dynamic_scheme(
|
|
||||||
collectedSource.source,
|
|
||||||
collectedSource.error,
|
|
||||||
collectedSource.variant,
|
|
||||||
collectedSource.contrastLevel,
|
|
||||||
collectedSource.harmonizeCustoms,
|
|
||||||
collectedSource.custom_colors,
|
|
||||||
);
|
|
||||||
updateScheme((prev) => {
|
|
||||||
prev.schemeStorage.source = collectedSource;
|
|
||||||
prev.schemeStorage.scheme = {
|
|
||||||
white: generate_scheme[0].white,
|
|
||||||
black: generate_scheme[0].black,
|
|
||||||
light_baseline: {
|
|
||||||
...generate_scheme[0].light_baseline,
|
|
||||||
customs: mapToObject(generate_scheme[0].light_baseline.customs),
|
|
||||||
},
|
|
||||||
dark_baseline: {
|
|
||||||
...generate_scheme[0].dark_baseline,
|
|
||||||
customs: mapToObject(generate_scheme[0].dark_baseline.customs),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
prev.schemeStorage.cssVariables = generate_scheme[1];
|
|
||||||
prev.schemeStorage.cssAutoSchemeVariables = generate_scheme[2];
|
|
||||||
prev.schemeStorage.scssVariables = generate_scheme[3];
|
|
||||||
prev.schemeStorage.jsVariables = generate_scheme[4];
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
|
|
||||||
onBuildCompleted?.();
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[generate m3d]', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
},
|
|
||||||
new Map<string, string>(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollArea enableY>
|
|
||||||
<form action={handleSubmitAction} className={styles.builder_layout}>
|
|
||||||
<h5 className={styles.segment_title}>Required Colors</h5>
|
|
||||||
<label className={styles.label}>Source Color</label>
|
|
||||||
<div className={styles.color_picker_row}>
|
|
||||||
<FloatColorPicker
|
|
||||||
name="source"
|
|
||||||
color={
|
|
||||||
isNil(scheme.schemeStorage.source?.source) ||
|
|
||||||
isEmpty(scheme.schemeStorage.source?.source)
|
|
||||||
? undefined
|
|
||||||
: scheme.schemeStorage.source?.source
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{errMsg.has('source') && <span className={styles.error_msg}>{errMsg.get('source')}</span>}
|
|
||||||
</div>
|
|
||||||
<label className={styles.label}>Error Color</label>
|
|
||||||
<div className={styles.color_picker_row}>
|
|
||||||
<FloatColorPicker
|
|
||||||
name="error"
|
|
||||||
color={
|
|
||||||
isNil(scheme.schemeStorage.source?.error) ||
|
|
||||||
isEmpty(scheme.schemeStorage.source.error)
|
|
||||||
? undefined
|
|
||||||
: scheme.schemeStorage.source.error
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h5 className={styles.segment_title}>Dynamic Settings</h5>
|
|
||||||
<label className={styles.label}>Dynamic Variant</label>
|
|
||||||
<div>
|
|
||||||
<VSegmentedControl
|
|
||||||
name="variant"
|
|
||||||
options={variantOptions}
|
|
||||||
defaultValue={scheme.schemeStorage.source?.variant ?? 6}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<label className={styles.label}>Contrast Level</label>
|
|
||||||
<div className={styles.parallel_row}>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
className="picker"
|
|
||||||
name="contrast_level"
|
|
||||||
min={-1}
|
|
||||||
max={1}
|
|
||||||
step={0.25}
|
|
||||||
value={contrastLevel}
|
|
||||||
onChange={(e) => setContrastLevel(parseFloat(e.target.value))}
|
|
||||||
/>
|
|
||||||
<span>{contrastLevel}</span>
|
|
||||||
</div>
|
|
||||||
<label className={styles.label}>Harmonize Custom Colors</label>
|
|
||||||
<div>
|
|
||||||
<Switch
|
|
||||||
name="harmonize_customs"
|
|
||||||
checked={scheme.schemeStorage.source?.harmonizeCustoms ?? false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h5 className={styles.segment_title}>Custom Colors</h5>
|
|
||||||
<label style={{ gridColumn: 1 }}>Name</label>
|
|
||||||
<label>Color</label>
|
|
||||||
<div>
|
|
||||||
<button type="button" className="small" onClick={addEntryAction}>
|
|
||||||
Add Color
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{originalColors
|
|
||||||
.filter((color) => !includes(deleted, color.id))
|
|
||||||
.map((color) => (
|
|
||||||
<ColorEntry
|
|
||||||
key={color.id}
|
|
||||||
entry={color}
|
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{newColors
|
|
||||||
.filter((color) => !includes(deleted, color.id))
|
|
||||||
.map((color) => (
|
|
||||||
<ColorEntry
|
|
||||||
key={color.id}
|
|
||||||
entry={color}
|
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
|
|
||||||
<button type="submit" className="primary">
|
|
||||||
Build Scheme
|
|
||||||
</button>
|
|
||||||
<button type="submit" className="secondary" formAction={handleDraftAction}>
|
|
||||||
Save Draft
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</ScrollArea>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -23,12 +23,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-s);
|
gap: var(--spacing-s);
|
||||||
}
|
}
|
||||||
.button_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
line-height: 1.7em;
|
line-height: 1.7em;
|
||||||
|
|
|
@ -2,13 +2,8 @@ import { includes, isEmpty, isNil } from 'lodash-es';
|
||||||
import { useActionState, useCallback, useMemo, useState } from 'react';
|
import { useActionState, useCallback, useMemo, useState } from 'react';
|
||||||
import { useColorFunction } from '../../../ColorFunctionContext';
|
import { useColorFunction } from '../../../ColorFunctionContext';
|
||||||
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
||||||
import { NotificationType, useNotification } from '../../../components/Notifications';
|
|
||||||
import { ScrollArea } from '../../../components/ScrollArea';
|
import { ScrollArea } from '../../../components/ScrollArea';
|
||||||
import {
|
import { MaterialDesign3Scheme, MaterialDesign3SchemeStorage } from '../../../material-3-scheme';
|
||||||
MaterialDesign3Scheme,
|
|
||||||
MaterialDesign3SchemeSource,
|
|
||||||
MaterialDesign3SchemeStorage,
|
|
||||||
} from '../../../material-3-scheme';
|
|
||||||
import { SchemeContent } from '../../../models';
|
import { SchemeContent } from '../../../models';
|
||||||
import { useUpdateScheme } from '../../../stores/schemes';
|
import { useUpdateScheme } from '../../../stores/schemes';
|
||||||
import { mapToObject } from '../../../utls';
|
import { mapToObject } from '../../../utls';
|
||||||
|
@ -21,7 +16,6 @@ type M3SchemeBuilderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderProps) {
|
export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderProps) {
|
||||||
const { showToast } = useNotification();
|
|
||||||
const { colorFn } = useColorFunction();
|
const { colorFn } = useColorFunction();
|
||||||
const updateScheme = useUpdateScheme(scheme.id);
|
const updateScheme = useUpdateScheme(scheme.id);
|
||||||
const originalColors = useMemo(() => {
|
const originalColors = useMemo(() => {
|
||||||
|
@ -42,62 +36,40 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
|
||||||
[originalColors, newColors, deleted],
|
[originalColors, newColors, deleted],
|
||||||
);
|
);
|
||||||
|
|
||||||
const collectSchemeSource = (formData: FormData): MaterialDesign3SchemeSource => {
|
|
||||||
const sourceColor = formData.get('source') as string;
|
|
||||||
const errorColor = formData.get('error') as string;
|
|
||||||
const customColors: Record<string, string> = {};
|
|
||||||
for (const key of colorKeys) {
|
|
||||||
const name = formData.get(`name_${key}`) as string;
|
|
||||||
const color = formData.get(`color_${key}`) as string;
|
|
||||||
if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
|
|
||||||
customColors[name] = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
source: isNil(sourceColor) || isEmpty(sourceColor) ? null : sourceColor,
|
|
||||||
error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
|
|
||||||
custom_colors: customColors,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
|
|
||||||
(_state, formData) => {
|
|
||||||
const errMsg = new Map<string, string>();
|
|
||||||
|
|
||||||
const collectedSource = collectSchemeSource(formData);
|
|
||||||
updateScheme((prev) => {
|
|
||||||
prev.schemeStorage.source = collectedSource;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
setNewColors([]);
|
|
||||||
|
|
||||||
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
},
|
|
||||||
new Map<string, string>(),
|
|
||||||
);
|
|
||||||
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
||||||
(_state, formData) => {
|
(_state, formData) => {
|
||||||
const errMsg = new Map<string, string>();
|
const errMsg = new Map<string, string>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const collectedSource = collectSchemeSource(formData);
|
const sourceColor = formData.get('source') as string;
|
||||||
if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) {
|
if (isNil(sourceColor) || isEmpty(sourceColor)) {
|
||||||
errMsg.set('source', 'Source color is required');
|
errMsg.set('source', 'Source color is required');
|
||||||
}
|
}
|
||||||
if (isNil(collectedSource.error) || isEmpty(collectedSource.error)) {
|
const errorColor = formData.get('error') as string;
|
||||||
|
if (isNil(errorColor) || isEmpty(errorColor)) {
|
||||||
errMsg.set('error', 'Error color is required');
|
errMsg.set('error', 'Error color is required');
|
||||||
}
|
}
|
||||||
if (!isEmpty(errMsg)) return errMsg;
|
if (!isEmpty(errMsg)) return errMsg;
|
||||||
|
|
||||||
|
const customColors: Record<string, string> = {};
|
||||||
|
for (const key of colorKeys) {
|
||||||
|
const name = formData.get(`name_${key}`) as string;
|
||||||
|
const color = formData.get(`color_${key}`) as string;
|
||||||
|
if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
|
||||||
|
customColors[name] = color;
|
||||||
|
}
|
||||||
|
|
||||||
const generatedScheme = colorFn?.generate_material_design_3_scheme(
|
const generatedScheme = colorFn?.generate_material_design_3_scheme(
|
||||||
collectedSource.source,
|
sourceColor,
|
||||||
collectedSource.error,
|
errorColor,
|
||||||
collectedSource.custom_colors,
|
customColors,
|
||||||
);
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = collectedSource;
|
prev.schemeStorage.source = {
|
||||||
|
source: sourceColor as string,
|
||||||
|
error: errorColor as string,
|
||||||
|
custom_colors: customColors,
|
||||||
|
};
|
||||||
prev.schemeStorage.scheme = {
|
prev.schemeStorage.scheme = {
|
||||||
white: generatedScheme[0].white,
|
white: generatedScheme[0].white,
|
||||||
black: generatedScheme[0].black,
|
black: generatedScheme[0].black,
|
||||||
|
@ -111,9 +83,8 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
|
||||||
},
|
},
|
||||||
} as MaterialDesign3Scheme;
|
} as MaterialDesign3Scheme;
|
||||||
prev.schemeStorage.cssVariables = generatedScheme[1];
|
prev.schemeStorage.cssVariables = generatedScheme[1];
|
||||||
prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
|
prev.schemeStorage.scssVariables = generatedScheme[2];
|
||||||
prev.schemeStorage.scssVariables = generatedScheme[3];
|
prev.schemeStorage.jsVariables = generatedScheme[3];
|
||||||
prev.schemeStorage.jsVariables = generatedScheme[4];
|
|
||||||
return prev;
|
return prev;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -183,13 +154,10 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
|
||||||
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
onDelete={(index) => setDeleted((prev) => [...prev, index])}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
<button type="submit" className="primary">
|
<button type="submit" className="primary">
|
||||||
Build Scheme
|
Build Scheme
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="secondary" formAction={handleDraftAction}>
|
|
||||||
Save Draft
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { ScrollArea } from '../../../components/ScrollArea';
|
import { ScrollArea } from '../../../components/ScrollArea';
|
||||||
import { Baseline, ColorSet, MaterialDesign3Scheme, Surface } from '../../../material-3-scheme';
|
import {
|
||||||
|
Baseline,
|
||||||
|
ColorSet,
|
||||||
|
MaterialDesign3SchemeStorage,
|
||||||
|
Surface,
|
||||||
|
} from '../../../material-3-scheme';
|
||||||
|
import { SchemeContent } from '../../../models';
|
||||||
import styles from './Preview.module.css';
|
import styles from './Preview.module.css';
|
||||||
|
|
||||||
type ColorSetProps = {
|
type ColorSetProps = {
|
||||||
|
@ -251,15 +257,15 @@ function PreviewBlock({ title, baseline }: PreviewBlockProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type M3SchemePreviewProps = {
|
type M3SchemePreviewProps = {
|
||||||
scheme: MaterialDesign3Scheme;
|
scheme: SchemeContent<MaterialDesign3SchemeStorage>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function M3SchemePreview({ scheme }: M3SchemePreviewProps) {
|
export function M3SchemePreview({ scheme }: M3SchemePreviewProps) {
|
||||||
return (
|
return (
|
||||||
<ScrollArea enableY>
|
<ScrollArea enableY>
|
||||||
<div className={styles.preview_layout}>
|
<div className={styles.preview_layout}>
|
||||||
<PreviewBlock title="Light Scheme" baseline={scheme.light_baseline} />
|
<PreviewBlock title="Light Scheme" baseline={scheme.schemeStorage.scheme!.light_baseline} />
|
||||||
<PreviewBlock title="Dark Scheme" baseline={scheme.dark_baseline} />
|
<PreviewBlock title="Dark Scheme" baseline={scheme.schemeStorage.scheme!.dark_baseline} />
|
||||||
</div>
|
</div>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
);
|
);
|
||||||
|
|
|
@ -30,12 +30,6 @@
|
||||||
.parameter_input {
|
.parameter_input {
|
||||||
max-width: 8em;
|
max-width: 8em;
|
||||||
}
|
}
|
||||||
.button_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
line-height: 1.7em;
|
line-height: 1.7em;
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { every, isEmpty, isNil } from 'lodash-es';
|
||||||
import { useActionState, useMemo } from 'react';
|
import { useActionState, useMemo } from 'react';
|
||||||
import { useColorFunction } from '../../../ColorFunctionContext';
|
import { useColorFunction } from '../../../ColorFunctionContext';
|
||||||
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
import { FloatColorPicker } from '../../../components/FloatColorPicker';
|
||||||
import { NotificationType, useNotification } from '../../../components/Notifications';
|
|
||||||
import { ScrollArea } from '../../../components/ScrollArea';
|
import { ScrollArea } from '../../../components/ScrollArea';
|
||||||
import { VSegmentedControl } from '../../../components/VSegmentedControl';
|
import { VSegmentedControl } from '../../../components/VSegmentedControl';
|
||||||
import { SchemeContent } from '../../../models';
|
import { SchemeContent } from '../../../models';
|
||||||
|
@ -18,7 +17,6 @@ type QSchemeBuilderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps) {
|
export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps) {
|
||||||
const { showToast } = useNotification();
|
|
||||||
const { colorFn } = useColorFunction();
|
const { colorFn } = useColorFunction();
|
||||||
const updateScheme = useUpdateScheme(scheme.id);
|
const updateScheme = useUpdateScheme(scheme.id);
|
||||||
const defaultSetting = useMemo(() => {
|
const defaultSetting = useMemo(() => {
|
||||||
|
@ -79,65 +77,6 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
|
||||||
return [];
|
return [];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const collectSchemeSource = (formData: FormData): [QSchemeSource, SchemeSetting] => {
|
|
||||||
const schemeSetting = new SchemeSetting(
|
|
||||||
new ColorShifting(
|
|
||||||
Number(formData.get('hover_chroma')) / 100,
|
|
||||||
Number(formData.get('hover_lightness')) / 100,
|
|
||||||
),
|
|
||||||
new ColorShifting(
|
|
||||||
Number(formData.get('active_chroma')) / 100,
|
|
||||||
Number(formData.get('active_lightness')) / 100,
|
|
||||||
),
|
|
||||||
new ColorShifting(
|
|
||||||
Number(formData.get('focus_chroma')) / 100,
|
|
||||||
Number(formData.get('focus_lightness')) / 100,
|
|
||||||
),
|
|
||||||
new ColorShifting(
|
|
||||||
Number(formData.get('disabled_chroma')) / 100,
|
|
||||||
Number(formData.get('disabled_lightness')) / 100,
|
|
||||||
),
|
|
||||||
new ColorShifting(
|
|
||||||
Number(formData.get('dark_chroma')) / 100,
|
|
||||||
Number(formData.get('dark_lightness')) / 100,
|
|
||||||
),
|
|
||||||
Number(formData.get('expanding')) as ColorExpand,
|
|
||||||
Number(formData.get('wacg')) as WACGSetting,
|
|
||||||
);
|
|
||||||
const dumpedSetting = schemeSetting.toJsValue() as QSchemeSetting;
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
primary: defaultEmptyFormData(formData, 'primary', null),
|
|
||||||
secondary: defaultEmptyFormData(formData, 'secondary', null),
|
|
||||||
tertiary: defaultEmptyFormData(formData, 'tertiary', null),
|
|
||||||
accent: defaultEmptyFormData(formData, 'accent', null),
|
|
||||||
danger: defaultEmptyFormData(formData, 'danger', null),
|
|
||||||
success: defaultEmptyFormData(formData, 'success', null),
|
|
||||||
warning: defaultEmptyFormData(formData, 'warn', null),
|
|
||||||
info: defaultEmptyFormData(formData, 'info', null),
|
|
||||||
foreground: defaultEmptyFormData(formData, 'foreground', null),
|
|
||||||
background: defaultEmptyFormData(formData, 'background', null),
|
|
||||||
setting: dumpedSetting,
|
|
||||||
},
|
|
||||||
schemeSetting,
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
|
|
||||||
(_state, formData) => {
|
|
||||||
const errMsg = new Map<string, string>();
|
|
||||||
|
|
||||||
const [source] = collectSchemeSource(formData);
|
|
||||||
updateScheme((prev) => {
|
|
||||||
prev.schemeStorage.source = source;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
},
|
|
||||||
new Map<string, string>(),
|
|
||||||
);
|
|
||||||
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
||||||
(_state, formData) => {
|
(_state, formData) => {
|
||||||
const errMsg = new Map<string, string>();
|
const errMsg = new Map<string, string>();
|
||||||
|
@ -157,7 +96,45 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
|
||||||
}
|
}
|
||||||
if (!isEmpty(errMsg)) return errMsg;
|
if (!isEmpty(errMsg)) return errMsg;
|
||||||
try {
|
try {
|
||||||
const [source, setting] = collectSchemeSource(formData);
|
const schemeSetting = new SchemeSetting(
|
||||||
|
new ColorShifting(
|
||||||
|
Number(formData.get('hover_chroma')) / 100,
|
||||||
|
Number(formData.get('hover_lightness')) / 100,
|
||||||
|
),
|
||||||
|
new ColorShifting(
|
||||||
|
Number(formData.get('active_chroma')) / 100,
|
||||||
|
Number(formData.get('active_lightness')) / 100,
|
||||||
|
),
|
||||||
|
new ColorShifting(
|
||||||
|
Number(formData.get('focus_chroma')) / 100,
|
||||||
|
Number(formData.get('focus_lightness')) / 100,
|
||||||
|
),
|
||||||
|
new ColorShifting(
|
||||||
|
Number(formData.get('disabled_chroma')) / 100,
|
||||||
|
Number(formData.get('disabled_lightness')) / 100,
|
||||||
|
),
|
||||||
|
new ColorShifting(
|
||||||
|
Number(formData.get('dark_chroma')) / 100,
|
||||||
|
Number(formData.get('dark_lightness')) / 100,
|
||||||
|
),
|
||||||
|
Number(formData.get('expanding')) as ColorExpand,
|
||||||
|
Number(formData.get('wacg')) as WACGSetting,
|
||||||
|
);
|
||||||
|
const dumpedSetting = schemeSetting.toJsValue() as QSchemeSetting;
|
||||||
|
|
||||||
|
const source: QSchemeSource = {
|
||||||
|
primary: defaultEmptyFormData(formData, 'primary', null),
|
||||||
|
secondary: defaultEmptyFormData(formData, 'secondary', null),
|
||||||
|
tertiary: defaultEmptyFormData(formData, 'tertiary', null),
|
||||||
|
accent: defaultEmptyFormData(formData, 'accent', null),
|
||||||
|
danger: defaultEmptyFormData(formData, 'danger', null),
|
||||||
|
success: defaultEmptyFormData(formData, 'success', null),
|
||||||
|
warning: defaultEmptyFormData(formData, 'warn', null),
|
||||||
|
info: defaultEmptyFormData(formData, 'info', null),
|
||||||
|
foreground: defaultEmptyFormData(formData, 'foreground', null),
|
||||||
|
background: defaultEmptyFormData(formData, 'background', null),
|
||||||
|
setting: dumpedSetting,
|
||||||
|
};
|
||||||
const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil)
|
const generatedScheme = every([source.secondary, source.tertiary, source.accent], isNil)
|
||||||
? colorFn?.generate_q_scheme_automatically(
|
? colorFn?.generate_q_scheme_automatically(
|
||||||
source.primary ?? '',
|
source.primary ?? '',
|
||||||
|
@ -167,7 +144,7 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
|
||||||
source.info ?? '',
|
source.info ?? '',
|
||||||
source.foreground ?? '',
|
source.foreground ?? '',
|
||||||
source.background ?? '',
|
source.background ?? '',
|
||||||
setting,
|
schemeSetting,
|
||||||
)
|
)
|
||||||
: colorFn?.generate_q_scheme_manually(
|
: colorFn?.generate_q_scheme_manually(
|
||||||
source.primary ?? '',
|
source.primary ?? '',
|
||||||
|
@ -180,15 +157,14 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
|
||||||
source.info ?? '',
|
source.info ?? '',
|
||||||
source.foreground ?? '',
|
source.foreground ?? '',
|
||||||
source.background ?? '',
|
source.background ?? '',
|
||||||
setting,
|
schemeSetting,
|
||||||
);
|
);
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = source;
|
prev.schemeStorage.source = source;
|
||||||
prev.schemeStorage.scheme = generatedScheme[0];
|
prev.schemeStorage.scheme = generatedScheme[0];
|
||||||
prev.schemeStorage.cssVariables = generatedScheme[1];
|
prev.schemeStorage.cssVariables = generatedScheme[1];
|
||||||
prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
|
prev.schemeStorage.scssVariables = generatedScheme[2];
|
||||||
prev.schemeStorage.scssVariables = generatedScheme[3];
|
prev.schemeStorage.jsVariables = generatedScheme[3];
|
||||||
prev.schemeStorage.jsVariables = generatedScheme[4];
|
|
||||||
return prev;
|
return prev;
|
||||||
});
|
});
|
||||||
onBuildCompleted?.();
|
onBuildCompleted?.();
|
||||||
|
@ -375,13 +351,10 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
|
||||||
defaultValue={defaultSetting?.wacg_follows}
|
defaultValue={defaultSetting?.wacg_follows}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
<button type="submit" className="primary">
|
<button type="submit" className="primary">
|
||||||
Build Scheme
|
Build Scheme
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="secondary" formAction={handleDraftAction}>
|
|
||||||
Save Draft
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
|
@ -30,12 +30,6 @@
|
||||||
.parameter_input {
|
.parameter_input {
|
||||||
max-width: 8em;
|
max-width: 8em;
|
||||||
}
|
}
|
||||||
.button_row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-s);
|
|
||||||
}
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: var(--font-size-m);
|
font-size: var(--font-size-m);
|
||||||
line-height: 1.7em;
|
line-height: 1.7em;
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { ColorShifting, SwatchEntry, SwatchSchemeSetting } from 'color-module';
|
||||||
import { includes, isEmpty, isEqual, isNaN } from 'lodash-es';
|
import { includes, isEmpty, isEqual, isNaN } from 'lodash-es';
|
||||||
import { useActionState, useCallback, useMemo, useState } from 'react';
|
import { useActionState, useCallback, useMemo, useState } from 'react';
|
||||||
import { useColorFunction } from '../../../ColorFunctionContext';
|
import { useColorFunction } from '../../../ColorFunctionContext';
|
||||||
import { NotificationType, useNotification } from '../../../components/Notifications';
|
|
||||||
import { ScrollArea } from '../../../components/ScrollArea';
|
import { ScrollArea } from '../../../components/ScrollArea';
|
||||||
import { Switch } from '../../../components/Switch';
|
import { Switch } from '../../../components/Switch';
|
||||||
import { SchemeContent } from '../../../models';
|
import { SchemeContent } from '../../../models';
|
||||||
|
@ -11,7 +10,6 @@ import {
|
||||||
QSwatchEntry,
|
QSwatchEntry,
|
||||||
QSwatchSchemeSetting,
|
QSwatchSchemeSetting,
|
||||||
SwatchScheme,
|
SwatchScheme,
|
||||||
SwatchSchemeSource,
|
|
||||||
SwatchSchemeStorage,
|
SwatchSchemeStorage,
|
||||||
} from '../../../swatch_scheme';
|
} from '../../../swatch_scheme';
|
||||||
import { mapToObject } from '../../../utls';
|
import { mapToObject } from '../../../utls';
|
||||||
|
@ -24,7 +22,6 @@ type SwatchSchemeBuilderProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBuilderProps) {
|
export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBuilderProps) {
|
||||||
const { showToast } = useNotification();
|
|
||||||
const { colorFn } = useColorFunction();
|
const { colorFn } = useColorFunction();
|
||||||
const updateScheme = useUpdateScheme(scheme.id);
|
const updateScheme = useUpdateScheme(scheme.id);
|
||||||
const originalColors = useMemo(() => {
|
const originalColors = useMemo(() => {
|
||||||
|
@ -67,100 +64,67 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}, [scheme.schemeStorage.source]);
|
}, [scheme.schemeStorage.source]);
|
||||||
const collectSchemeSource = (formData: FormData): SwatchSchemeSource => {
|
|
||||||
const swatchAmount = Number(formData.get('amount'));
|
|
||||||
const minLightness = Number(formData.get('min_lightness'));
|
|
||||||
const maxLightness = Number(formData.get('max_lightness'));
|
|
||||||
const includePrimary = isEqual(formData.get('include_primary'), 'true');
|
|
||||||
const darkConvertChroma = Number(formData.get('dark_chroma')) / 100.0;
|
|
||||||
const darkConvertLightness = Number(formData.get('dark_lightness')) / 100.0;
|
|
||||||
|
|
||||||
const swatchSetting = new SwatchSchemeSetting(
|
|
||||||
swatchAmount,
|
|
||||||
minLightness / 100.0,
|
|
||||||
maxLightness / 100.0,
|
|
||||||
includePrimary,
|
|
||||||
new ColorShifting(darkConvertChroma, darkConvertLightness),
|
|
||||||
);
|
|
||||||
const dumpedSettings = swatchSetting.toJsValue() as QSwatchSchemeSetting;
|
|
||||||
const entries: SwatchEntry[] = [];
|
|
||||||
for (const key of colorKeys) {
|
|
||||||
const name = String(formData.get(`name_${key}`));
|
|
||||||
const color = String(formData.get(`color_${key}`));
|
|
||||||
if (isEmpty(name) || isEmpty(color)) continue;
|
|
||||||
entries.push(new SwatchEntry(name, color));
|
|
||||||
}
|
|
||||||
const dumpedEntries = entries.map((entry) => entry.toJsValue() as QSwatchEntry);
|
|
||||||
|
|
||||||
return {
|
|
||||||
colors: dumpedEntries,
|
|
||||||
setting: dumpedSettings,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const [, handleDraftAction] = useActionState<Map<string, string>, FormData>(
|
|
||||||
(_state, formData) => {
|
|
||||||
const errMsg = new Map<string, string>();
|
|
||||||
|
|
||||||
const collected = collectSchemeSource(formData);
|
|
||||||
updateScheme((prev) => {
|
|
||||||
prev.schemeStorage.source = collected;
|
|
||||||
return prev;
|
|
||||||
});
|
|
||||||
setNewColors([]);
|
|
||||||
|
|
||||||
showToast(NotificationType.SUCCESS, 'Scheme draft saved!', 'tabler:device-floppy', 3000);
|
|
||||||
|
|
||||||
return errMsg;
|
|
||||||
},
|
|
||||||
new Map<string, string>(),
|
|
||||||
);
|
|
||||||
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
|
||||||
(_state, formData) => {
|
(_state, formData) => {
|
||||||
const errMsg = new Map<string, string>();
|
const errMsg = new Map<string, string>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const collected = collectSchemeSource(formData);
|
const swatchAmount = Number(formData.get('amount'));
|
||||||
if (isNaN(collected.setting.amount) || collected.setting.amount <= 0) {
|
if (isNaN(swatchAmount) || swatchAmount <= 0) {
|
||||||
errMsg.set('amount', 'MUST be a positive number');
|
errMsg.set('amount', 'MUST be a positive number');
|
||||||
}
|
}
|
||||||
if (collected.setting.amount > 30) {
|
if (swatchAmount > 30) {
|
||||||
errMsg.set('amount', 'MUST be less than 30');
|
errMsg.set('amount', 'MUST be less than 30');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const minLightness = Number(formData.get('min_lightness'));
|
||||||
isNaN(collected.setting.min_lightness) ||
|
if (isNaN(minLightness) || minLightness < 0 || minLightness > 100) {
|
||||||
collected.setting.min_lightness < 0 ||
|
|
||||||
collected.setting.min_lightness > 1.0
|
|
||||||
) {
|
|
||||||
errMsg.set('min', 'MUST be a number between 0 and 100');
|
errMsg.set('min', 'MUST be a number between 0 and 100');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const maxLightness = Number(formData.get('max_lightness'));
|
||||||
isNaN(collected.setting.max_lightness) ||
|
if (isNaN(maxLightness) || maxLightness < 0 || maxLightness > 100) {
|
||||||
collected.setting.max_lightness < 0 ||
|
|
||||||
collected.setting.max_lightness > 1.0
|
|
||||||
) {
|
|
||||||
errMsg.set('max', 'MUST be a number between 0 and 100');
|
errMsg.set('max', 'MUST be a number between 0 and 100');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmpty(collected.colors)) {
|
const includePrimary = isEqual(formData.get('include_primary'), 'true');
|
||||||
|
const darkConvertChroma = Number(formData.get('dark_chroma')) / 100.0;
|
||||||
|
const darkConvertLightness = Number(formData.get('dark_lightness')) / 100.0;
|
||||||
|
|
||||||
|
const swatchSetting = new SwatchSchemeSetting(
|
||||||
|
swatchAmount,
|
||||||
|
minLightness / 100.0,
|
||||||
|
maxLightness / 100.0,
|
||||||
|
includePrimary,
|
||||||
|
new ColorShifting(darkConvertChroma, darkConvertLightness),
|
||||||
|
);
|
||||||
|
const dumpedSettings = swatchSetting.toJsValue() as QSwatchSchemeSetting;
|
||||||
|
const entries: SwatchEntry[] = [];
|
||||||
|
for (const key of colorKeys) {
|
||||||
|
const name = String(formData.get(`name_${key}`));
|
||||||
|
const color = String(formData.get(`color_${key}`));
|
||||||
|
if (isEmpty(name) || isEmpty(color)) continue;
|
||||||
|
entries.push(new SwatchEntry(name, color));
|
||||||
|
}
|
||||||
|
const dumpedEntries = entries.map((entry) => entry.toJsValue() as QSwatchEntry);
|
||||||
|
if (isEmpty(entries)) {
|
||||||
errMsg.set('color', 'At least one color is required');
|
errMsg.set('color', 'At least one color is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(errMsg)) return errMsg;
|
if (!isEmpty(errMsg)) return errMsg;
|
||||||
|
|
||||||
const generatedScheme = colorFn?.generate_swatch_scheme(
|
const generatedScheme = colorFn?.generate_swatch_scheme(entries, swatchSetting);
|
||||||
collected.colors,
|
console.debug('[generated scheme]', generatedScheme);
|
||||||
collected.setting,
|
|
||||||
);
|
|
||||||
updateScheme((prev) => {
|
updateScheme((prev) => {
|
||||||
prev.schemeStorage.source = collected;
|
prev.schemeStorage.source = {
|
||||||
|
colors: dumpedEntries,
|
||||||
|
setting: dumpedSettings,
|
||||||
|
};
|
||||||
prev.schemeStorage.scheme = mapToObject(generatedScheme[0]) as SwatchScheme;
|
prev.schemeStorage.scheme = mapToObject(generatedScheme[0]) as SwatchScheme;
|
||||||
prev.schemeStorage.cssVariables = generatedScheme[1];
|
prev.schemeStorage.cssVariables = generatedScheme[1];
|
||||||
prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
|
prev.schemeStorage.scssVariables = generatedScheme[2];
|
||||||
prev.schemeStorage.scssVariables = generatedScheme[3];
|
prev.schemeStorage.jsVariables = generatedScheme[3];
|
||||||
prev.schemeStorage.jsVariables = generatedScheme[4];
|
|
||||||
return prev;
|
return prev;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -272,13 +236,10 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
|
||||||
<span className={styles.error_msg}>{errMsg.get('color')}</span>
|
<span className={styles.error_msg}>{errMsg.get('color')}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={styles.button_row} style={{ gridColumn: '2 / span 2' }}>
|
<div style={{ gridColumn: '2 / span 2' }}>
|
||||||
<button type="submit" className="primary">
|
<button type="submit" className="primary">
|
||||||
Build Scheme
|
Build Scheme
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className="secondary" formAction={handleDraftAction}>
|
|
||||||
Save Draft
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
|
@ -31,7 +31,7 @@ function SchemeItem({ item }: SchemeItemProps) {
|
||||||
const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
|
const isActived = useMemo(() => isEqual(activedScheme, item.id), [activedScheme, item.id]);
|
||||||
const isSelected = useMemo(() => isEqual(navParams['id'], item.id), [navParams, item.id]);
|
const isSelected = useMemo(() => isEqual(navParams['id'], item.id), [navParams, item.id]);
|
||||||
const handleActiveScheme = useCallback(() => {
|
const handleActiveScheme = useCallback(() => {
|
||||||
setActiveScheme((prev) => (prev === item.id ? null : item.id));
|
setActiveScheme((prev) => (prev ? null : item.id));
|
||||||
}, [item]);
|
}, [item]);
|
||||||
const handleRemoveScheme = useCallback(() => {
|
const handleRemoveScheme = useCallback(() => {
|
||||||
removeScheme();
|
removeScheme();
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { HSegmentedControl } from '../components/HSegmentedControl';
|
||||||
import { ScrollArea } from '../components/ScrollArea';
|
import { ScrollArea } from '../components/ScrollArea';
|
||||||
import { ColorDescription } from '../models';
|
import { ColorDescription } from '../models';
|
||||||
import { ColorCard } from '../page-components/cards-detail/ColorCard';
|
import { ColorCard } from '../page-components/cards-detail/ColorCard';
|
||||||
import { mapToObject } from '../utls';
|
|
||||||
import styles from './CardsDetail.module.css';
|
import styles from './CardsDetail.module.css';
|
||||||
|
|
||||||
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
|
type ColorModes = 'hex' | 'rgb' | 'hsl' | 'lab' | 'oklch';
|
||||||
|
@ -21,11 +20,7 @@ export function CardsDetail({ mainTag }: CardsDetailProps) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const embededCategories = colorFn.color_categories().map(mapToObject) as {
|
const embededCategories = colorFn.color_categories() as { label: string; value: string }[];
|
||||||
label: string;
|
|
||||||
value: string;
|
|
||||||
}[];
|
|
||||||
console.debug('[Fetch color categories]', embededCategories);
|
|
||||||
return embededCategories.filter((cate) => !isEqual(cate.value, 'unknown'));
|
return embededCategories.filter((cate) => !isEqual(cate.value, 'unknown'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[Fetch color categories]', e);
|
console.error('[Fetch color categories]', e);
|
||||||
|
|
|
@ -6,14 +6,10 @@ import { EditableDescription } from '../components/EditableDescription';
|
||||||
import { EditableTitle } from '../components/EditableTitle';
|
import { EditableTitle } from '../components/EditableTitle';
|
||||||
import { SchemeSign } from '../components/SchemeSign';
|
import { SchemeSign } from '../components/SchemeSign';
|
||||||
import { MaterialDesign2SchemeStorage } from '../material-2-scheme';
|
import { MaterialDesign2SchemeStorage } from '../material-2-scheme';
|
||||||
import {
|
import { MaterialDesign3SchemeStorage } from '../material-3-scheme';
|
||||||
MaterialDesign3DynamicSchemeStorage,
|
|
||||||
MaterialDesign3SchemeStorage,
|
|
||||||
} from '../material-3-scheme';
|
|
||||||
import { SchemeContent } from '../models';
|
import { SchemeContent } from '../models';
|
||||||
import { CorruptedScheme } from '../page-components/scheme/CorruptedScheme';
|
import { CorruptedScheme } from '../page-components/scheme/CorruptedScheme';
|
||||||
import { M2Scheme } from '../page-components/scheme/M2Scheme';
|
import { M2Scheme } from '../page-components/scheme/M2Scheme';
|
||||||
import { M3DynamicScheme } from '../page-components/scheme/M3DynamicScheme';
|
|
||||||
import { M3Scheme } from '../page-components/scheme/M3Scheme';
|
import { M3Scheme } from '../page-components/scheme/M3Scheme';
|
||||||
import { QScheme } from '../page-components/scheme/QScheme';
|
import { QScheme } from '../page-components/scheme/QScheme';
|
||||||
import { SwatchScheme } from '../page-components/scheme/SwatchScheme';
|
import { SwatchScheme } from '../page-components/scheme/SwatchScheme';
|
||||||
|
@ -56,10 +52,6 @@ export function SchemeDetail() {
|
||||||
return <M2Scheme scheme={scheme as SchemeContent<MaterialDesign2SchemeStorage>} />;
|
return <M2Scheme scheme={scheme as SchemeContent<MaterialDesign2SchemeStorage>} />;
|
||||||
case 'material_3':
|
case 'material_3':
|
||||||
return <M3Scheme scheme={scheme as SchemeContent<MaterialDesign3SchemeStorage>} />;
|
return <M3Scheme scheme={scheme as SchemeContent<MaterialDesign3SchemeStorage>} />;
|
||||||
case 'material_3_dynamic':
|
|
||||||
return (
|
|
||||||
<M3DynamicScheme scheme={scheme as SchemeContent<MaterialDesign3DynamicSchemeStorage>} />
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return <CorruptedScheme />;
|
return <CorruptedScheme />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,6 @@ export type QSchemeStorage = {
|
||||||
source?: QSchemeSource;
|
source?: QSchemeSource;
|
||||||
scheme?: QScheme;
|
scheme?: QScheme;
|
||||||
cssVariables?: string;
|
cssVariables?: string;
|
||||||
cssAutoSchemeVariables?: string;
|
|
||||||
scssVariables?: string;
|
scssVariables?: string;
|
||||||
jsVariables?: string;
|
jsVariables?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function useScheme(id?: string | null): SchemeContent<SchemeStorage> | nu
|
||||||
|
|
||||||
export function useActiveScheme(): SchemeContent<SchemeStorage> | null {
|
export function useActiveScheme(): SchemeContent<SchemeStorage> | null {
|
||||||
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
const activeSchemeId = useAtomValue(activeSchemeAtom);
|
||||||
const activeScheme = useScheme(activeSchemeId ?? null);
|
const activeScheme = useScheme(activeSchemeId ?? 'UNEXISTS');
|
||||||
return activeScheme;
|
return activeScheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ export type SwatchSchemeStorage = {
|
||||||
source?: SwatchSchemeSource;
|
source?: SwatchSchemeSource;
|
||||||
scheme?: SwatchScheme;
|
scheme?: SwatchScheme;
|
||||||
cssVariables?: string;
|
cssVariables?: string;
|
||||||
cssAutoSchemeVariables?: string;
|
|
||||||
scssVariables?: string;
|
scssVariables?: string;
|
||||||
jsVariables?: string;
|
jsVariables?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,10 +15,6 @@ export function defaultEmptyValue<T, D>(value: T, defaultValue: D): T | D {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNilOrEmpty(value?: unknown): boolean {
|
|
||||||
return isNil(value) || isEmpty(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function mapToObject<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> {
|
export function mapToObject<K extends string | number | symbol, V>(map: Map<K, V>): Record<K, V> {
|
||||||
const obj = {} as Record<K, V>;
|
const obj = {} as Record<K, V>;
|
||||||
map.forEach((value, key) => {
|
map.forEach((value, key) => {
|
||||||
|
|
|
@ -10,7 +10,4 @@ export default defineConfig({
|
||||||
cssModules: true,
|
cssModules: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
|
||||||
exclude: ['color-module'],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user