Compare commits
	
		
			32 Commits
		
	
	
		
			wasm-load
			...
			2638bbd99a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					2638bbd99a | ||
| 
						 | 
					f284a7ef62 | ||
| 
						 | 
					00d1e425c0 | ||
| 
						 | 
					0a5d475655 | ||
| 
						 | 
					a1f63cd724 | ||
| 
						 | 
					5f3d58f0f5 | ||
| 
						 | 
					ef3ef2b349 | ||
| 
						 | 
					3b0600e64a | ||
| 
						 | 
					21b538af99 | ||
| 
						 | 
					e170e3c11d | ||
| 
						 | 
					c4a2f6f638 | ||
| 
						 | 
					dfbbe2b884 | ||
| 
						 | 
					7fba372d08 | ||
| 
						 | 
					f3f259fd1c | ||
| 
						 | 
					55f0eab76d | ||
| 
						 | 
					ed2e323e3e | ||
| 
						 | 
					1a136670ff | ||
| 
						 | 
					639b6b223e | ||
| 
						 | 
					e980eeec3d | ||
| 
						 | 
					4b56d3a625 | ||
| 
						 | 
					84c164b2c8 | ||
| 
						 | 
					9b6f4ace14 | ||
| 
						 | 
					4054c2ce56 | ||
| 
						 | 
					d0e8acc5c0 | ||
| 
						 | 
					e2f78aefb3 | ||
| 
						 | 
					6396b257cb | ||
| 
						 | 
					cd2d724b52 | ||
| 
						 | 
					715459eef0 | ||
| 
						 | 
					31cedd2547 | ||
| 
						 | 
					7f6c217d7e | ||
| 
						 | 
					ccedaa62a6 | ||
| 
						 | 
					128eeb24ac | 
@@ -9,6 +9,8 @@ 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,3 +135,14 @@ 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,5 +1,6 @@
 | 
				
			|||||||
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};
 | 
				
			||||||
@@ -75,12 +76,28 @@ 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));
 | 
					            variable_lines.extend(color_set.to_css_variable(&prefix, &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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" };
 | 
				
			||||||
@@ -93,7 +110,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));
 | 
					            variable_lines.extend(color_set.to_scss_variable(&prefix, &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        variable_lines
 | 
					        variable_lines
 | 
				
			||||||
@@ -111,7 +128,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));
 | 
					            variable_lines.extend(color_set.to_javascript_object(&prefix, &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        variable_lines
 | 
					        variable_lines
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					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};
 | 
				
			||||||
@@ -82,6 +83,16 @@ 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,4 +1,5 @@
 | 
				
			|||||||
use baseline::M2BaselineColors;
 | 
					use baseline::M2BaselineColors;
 | 
				
			||||||
 | 
					use linked_hash_set::LinkedHashSet;
 | 
				
			||||||
use palette::Hsl;
 | 
					use palette::Hsl;
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,6 +49,34 @@ 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,5 +1,6 @@
 | 
				
			|||||||
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;
 | 
				
			||||||
@@ -80,6 +81,34 @@ 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" };
 | 
				
			||||||
@@ -97,12 +126,31 @@ 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));
 | 
					            css_variables.extend(color_set.to_css_variables(prefix, &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        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" };
 | 
				
			||||||
@@ -120,7 +168,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));
 | 
					            scss_variables.extend(color_set.to_scss_variables(prefix, &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        scss_variables
 | 
					        scss_variables
 | 
				
			||||||
@@ -149,7 +197,8 @@ 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.extend(color_set.to_javascript_object_fields(prefix, name));
 | 
					            js_object_fields
 | 
				
			||||||
 | 
					                .extend(color_set.to_javascript_object_fields(prefix, &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        js_object_fields
 | 
					        js_object_fields
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
@@ -106,6 +107,25 @@ 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,9 +1,14 @@
 | 
				
			|||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::str::FromStr;
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use baseline::M3BaselineColors;
 | 
					pub 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;
 | 
				
			||||||
use tonal_palette::TonalPalette;
 | 
					pub use surface::M3SurfaceSet;
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
@@ -13,6 +18,7 @@ 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)]
 | 
				
			||||||
@@ -21,6 +27,7 @@ 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 {
 | 
				
			||||||
@@ -41,11 +48,20 @@ 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,
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -62,8 +78,23 @@ 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 {
 | 
				
			||||||
@@ -74,10 +105,44 @@ 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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -85,6 +150,9 @@ 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")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -100,8 +168,7 @@ 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());
 | 
				
			||||||
@@ -109,10 +176,19 @@ 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,3 +1,4 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
@@ -9,6 +10,7 @@ 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,
 | 
				
			||||||
@@ -25,6 +27,7 @@ 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);
 | 
				
			||||||
@@ -39,6 +42,7 @@ 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),
 | 
				
			||||||
@@ -55,6 +59,7 @@ 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);
 | 
				
			||||||
@@ -69,6 +74,7 @@ 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),
 | 
				
			||||||
@@ -84,89 +90,126 @@ 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-container: ${};",
 | 
					            "--color-{}-surface-variant: #{};",
 | 
				
			||||||
 | 
					            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-container: ${};",
 | 
					            "$color-{}-surface-variant: #{};",
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -179,6 +222,7 @@ 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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								color-module/src/schemes/material_design_3/swatch.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								color-module/src/schemes/material_design_3/swatch.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::convert::map_lch_to_srgb_hex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::TonalPalette;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static SWATCH_TONES: [u8; 18] = [
 | 
				
			||||||
 | 
					    0, 5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100,
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize)]
 | 
				
			||||||
 | 
					pub struct M3PaletteSwatch(HashMap<u8, String>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl M3PaletteSwatch {
 | 
				
			||||||
 | 
					    pub fn new(palette: &TonalPalette) -> Self {
 | 
				
			||||||
 | 
					        let mut swatch = HashMap::new();
 | 
				
			||||||
 | 
					        for &tone in SWATCH_TONES.iter() {
 | 
				
			||||||
 | 
					            let color = palette.tone(tone as f32);
 | 
				
			||||||
 | 
					            swatch.insert(tone, map_lch_to_srgb_hex(&color));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        Self(swatch)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn to_css_variables(&self, name: &str) -> Vec<String> {
 | 
				
			||||||
 | 
					        let mut variable_lines = Vec::new();
 | 
				
			||||||
 | 
					        let name = name.replace('_', "-").to_lowercase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for &tone in SWATCH_TONES.iter() {
 | 
				
			||||||
 | 
					            let color = self.0.get(&tone).unwrap();
 | 
				
			||||||
 | 
					            variable_lines.push(format!("--color-swatch-{}-{}: #{};", name, tone, color));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        variable_lines
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn to_scss_variables(&self, name: &str) -> Vec<String> {
 | 
				
			||||||
 | 
					        let mut variable_lines = Vec::new();
 | 
				
			||||||
 | 
					        let name = name.replace('_', "-").to_lowercase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for &tone in SWATCH_TONES.iter() {
 | 
				
			||||||
 | 
					            let color = self.0.get(&tone).unwrap();
 | 
				
			||||||
 | 
					            variable_lines.push(format!("$color-swatch-{}-{}: #{};", name, tone, color));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        variable_lines
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn to_javascript_object_fields(&self, name: &str) -> Vec<String> {
 | 
				
			||||||
 | 
					        let mut js_object = Vec::new();
 | 
				
			||||||
 | 
					        let name = name
 | 
				
			||||||
 | 
					            .split('_')
 | 
				
			||||||
 | 
					            .enumerate()
 | 
				
			||||||
 | 
					            .map(|(i, part)| {
 | 
				
			||||||
 | 
					                if i == 0 {
 | 
				
			||||||
 | 
					                    part.to_string()
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    let mut c = part.chars();
 | 
				
			||||||
 | 
					                    c.next().unwrap().to_uppercase().collect::<String>() + c.as_str()
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .collect::<String>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_object.push(format!("{}: {{", name));
 | 
				
			||||||
 | 
					        for &tone in SWATCH_TONES.iter() {
 | 
				
			||||||
 | 
					            let color = self.0.get(&tone).unwrap();
 | 
				
			||||||
 | 
					            js_object.push(format!("  {}: '#{}',", tone, color));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        js_object.push("},".to_string());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        js_object
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										379
									
								
								color-module/src/schemes/material_design_3_dynamic/constants.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								color-module/src/schemes/material_design_3_dynamic/constants.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,379 @@
 | 
				
			|||||||
 | 
					use enum_iterator::Sequence;
 | 
				
			||||||
 | 
					use palette::{
 | 
				
			||||||
 | 
					    color_theory::{Analogous, Complementary},
 | 
				
			||||||
 | 
					    Lch,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use serde_repr::{Deserialize_repr, Serialize_repr};
 | 
				
			||||||
 | 
					use strum::Display;
 | 
				
			||||||
 | 
					use wasm_bindgen::prelude::wasm_bindgen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    schemes::material_design_3::TonalPalette,
 | 
				
			||||||
 | 
					    theory::{harmonize_hue, sanitize_hue_degrees},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::dynamic_color::CustomPaletteGenerator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Display, Sequence, Serialize_repr, Deserialize_repr)]
 | 
				
			||||||
 | 
					#[wasm_bindgen]
 | 
				
			||||||
 | 
					#[repr(u8)]
 | 
				
			||||||
 | 
					pub enum Variant {
 | 
				
			||||||
 | 
					    Monochrome,
 | 
				
			||||||
 | 
					    Neutral,
 | 
				
			||||||
 | 
					    TonalSpot,
 | 
				
			||||||
 | 
					    Vibrant,
 | 
				
			||||||
 | 
					    Expressive,
 | 
				
			||||||
 | 
					    Fidelity,
 | 
				
			||||||
 | 
					    Content,
 | 
				
			||||||
 | 
					    Rainbow,
 | 
				
			||||||
 | 
					    FruitSalad,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Variant {
 | 
				
			||||||
 | 
					    pub fn label(&self) -> String {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Variant::Monochrome => "Monochrome".to_string(),
 | 
				
			||||||
 | 
					            Variant::Neutral => "Neutral".to_string(),
 | 
				
			||||||
 | 
					            Variant::TonalSpot => "Tonal Spot".to_string(),
 | 
				
			||||||
 | 
					            Variant::Vibrant => "Vibrant".to_string(),
 | 
				
			||||||
 | 
					            Variant::Expressive => "Expressive".to_string(),
 | 
				
			||||||
 | 
					            Variant::Fidelity => "Fidelity".to_string(),
 | 
				
			||||||
 | 
					            Variant::Content => "Content".to_string(),
 | 
				
			||||||
 | 
					            Variant::Rainbow => "Rainbow".to_string(),
 | 
				
			||||||
 | 
					            Variant::FruitSalad => "Fruit Salad".to_string(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn from_u8(value: u8) -> Variant {
 | 
				
			||||||
 | 
					        match value {
 | 
				
			||||||
 | 
					            0 => Variant::Monochrome,
 | 
				
			||||||
 | 
					            1 => Variant::Neutral,
 | 
				
			||||||
 | 
					            2 => Variant::TonalSpot,
 | 
				
			||||||
 | 
					            3 => Variant::Vibrant,
 | 
				
			||||||
 | 
					            4 => Variant::Expressive,
 | 
				
			||||||
 | 
					            5 => Variant::Fidelity,
 | 
				
			||||||
 | 
					            6 => Variant::Content,
 | 
				
			||||||
 | 
					            7 => Variant::Rainbow,
 | 
				
			||||||
 | 
					            8 => Variant::FruitSalad,
 | 
				
			||||||
 | 
					            _ => Variant::Expressive,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn hues(&self) -> Vec<f32> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Variant::Vibrant => vec![0.0, 41.0, 61.0, 101.0, 131.0, 181.0, 251.0, 301.0, 360.0],
 | 
				
			||||||
 | 
					            Variant::Expressive => vec![0.0, 21.0, 51.0, 121.0, 151.0, 191.0, 271.0, 321.0, 360.0],
 | 
				
			||||||
 | 
					            _ => vec![],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn secondary_rotation(&self) -> Vec<f32> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Variant::Vibrant => vec![18.0, 15.0, 10.0, 12.0, 15.0, 18.0, 15.0, 12.0, 12.0],
 | 
				
			||||||
 | 
					            Variant::Expressive => vec![45.0, 95.0, 45.0, 20.0, 45.0, 90.0, 45.0, 45.0, 45.0],
 | 
				
			||||||
 | 
					            _ => vec![],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn tertiary_rotation(&self) -> Vec<f32> {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Variant::Vibrant => vec![35.0, 30.0, 20.0, 25.0, 30.0, 35.0, 30.0, 25.0, 25.0],
 | 
				
			||||||
 | 
					            Variant::Expressive => vec![120.0, 120.0, 20.0, 45.0, 20.0, 15.0, 20.0, 120.0, 120.0],
 | 
				
			||||||
 | 
					            _ => vec![],
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn build_palette(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        source_color: Lch,
 | 
				
			||||||
 | 
					        harmonize_customs: bool,
 | 
				
			||||||
 | 
					    ) -> (
 | 
				
			||||||
 | 
					        TonalPalette,
 | 
				
			||||||
 | 
					        TonalPalette,
 | 
				
			||||||
 | 
					        TonalPalette,
 | 
				
			||||||
 | 
					        TonalPalette,
 | 
				
			||||||
 | 
					        TonalPalette,
 | 
				
			||||||
 | 
					        CustomPaletteGenerator,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        match self {
 | 
				
			||||||
 | 
					            Variant::Monochrome => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            0.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 0.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::Neutral => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 12.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 8.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 2.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 2.0),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            12.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 12.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::TonalSpot => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 36.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 60.0),
 | 
				
			||||||
 | 
					                    24.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 6.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 8.0),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            36.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 36.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::Vibrant => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 200.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    rotate_hue(&source_color, &self.hues(), &self.secondary_rotation()),
 | 
				
			||||||
 | 
					                    24.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    rotate_hue(&source_color, &self.hues(), &self.tertiary_rotation()),
 | 
				
			||||||
 | 
					                    32.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 10.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 12.0),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            200.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 200.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::Expressive => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 240.0),
 | 
				
			||||||
 | 
					                    40.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    rotate_hue(&source_color, &self.hues(), &self.secondary_rotation()),
 | 
				
			||||||
 | 
					                    24.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    rotate_hue(&source_color, &self.hues(), &self.tertiary_rotation()),
 | 
				
			||||||
 | 
					                    32.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees() + 15.0,
 | 
				
			||||||
 | 
					                    8.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees() + 15.0,
 | 
				
			||||||
 | 
					                    12.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue =
 | 
				
			||||||
 | 
					                        sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 240.0);
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            40.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 40.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::Fidelity => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    source_color.chroma,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    (source_color.chroma - 32.0).max(source_color.chroma * 0.5),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette {
 | 
				
			||||||
 | 
					                    key_color: fix_disliked(&source_color.complementary()),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    source_color.chroma / 8.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    source_color.chroma / 8.0 + 4.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    let source_chroma = source_color.chroma;
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            source_chroma,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    let source_chroma = source_color.chroma;
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            c.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                            source_chroma,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::Content => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    source_color.chroma,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    (source_color.chroma - 32.0).max(source_color.chroma * 0.5),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette {
 | 
				
			||||||
 | 
					                    key_color: fix_disliked(&source_color.analogous().1),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    source_color.chroma / 8.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    source_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                    source_color.chroma / 8.0 + 4.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            c.chroma,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), c.chroma)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::Rainbow => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 48.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    sanitize_hue_degrees(source_color.hue.into_positive_degrees() + 60.0),
 | 
				
			||||||
 | 
					                    24.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 0.0),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue = source_color.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            48.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 48.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            Variant::FruitSalad => (
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    sanitize_hue_degrees(source_color.hue.into_positive_degrees() - 50.0),
 | 
				
			||||||
 | 
					                    48.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                    sanitize_hue_degrees(source_color.hue.into_positive_degrees() - 50.0),
 | 
				
			||||||
 | 
					                    36.0,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 36.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 10.0),
 | 
				
			||||||
 | 
					                TonalPalette::from_hue_and_chroma(source_color.hue.into_positive_degrees(), 16.0),
 | 
				
			||||||
 | 
					                if harmonize_customs {
 | 
				
			||||||
 | 
					                    let source_hue =
 | 
				
			||||||
 | 
					                        sanitize_hue_degrees(source_color.hue.into_positive_degrees() - 50.0);
 | 
				
			||||||
 | 
					                    Box::new(move |c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                            harmonize_hue(c.hue.into_positive_degrees(), source_hue),
 | 
				
			||||||
 | 
					                            48.0,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    Box::new(|c: &Lch| {
 | 
				
			||||||
 | 
					                        TonalPalette::from_hue_and_chroma(c.hue.into_positive_degrees(), 48.0)
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn rotate_hue(source: &Lch, hues: &Vec<f32>, rotations: &Vec<f32>) -> f32 {
 | 
				
			||||||
 | 
					    let source_hue = source.hue.into_positive_degrees();
 | 
				
			||||||
 | 
					    if rotations.len() == 1 {
 | 
				
			||||||
 | 
					        return sanitize_hue_degrees(source_hue + rotations[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    let hues_size = hues.len();
 | 
				
			||||||
 | 
					    for i in 0..=hues_size - 2 {
 | 
				
			||||||
 | 
					        let hue = hues[i];
 | 
				
			||||||
 | 
					        let next_hue = hues[i + 1];
 | 
				
			||||||
 | 
					        if hue < source_hue && source_hue < next_hue {
 | 
				
			||||||
 | 
					            return sanitize_hue_degrees(source_hue + rotations[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    source_hue
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn fix_disliked(color: &Lch) -> Lch {
 | 
				
			||||||
 | 
					    let hue = color.hue.into_positive_degrees().round() >= 90.0
 | 
				
			||||||
 | 
					        && color.hue.into_positive_degrees().round() <= 111.0;
 | 
				
			||||||
 | 
					    let chroma = color.chroma.round() > 16.0;
 | 
				
			||||||
 | 
					    let lightness = color.l.round() < 65.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if hue && chroma && lightness {
 | 
				
			||||||
 | 
					        Lch::new(70.0, color.chroma, color.hue)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        color.clone()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										102
									
								
								color-module/src/schemes/material_design_3_dynamic/contrast.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								color-module/src/schemes/material_design_3_dynamic/contrast.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					fn lab_inv_f(ft: f32) -> f32 {
 | 
				
			||||||
 | 
					    let e = 216.0 / 24389.0;
 | 
				
			||||||
 | 
					    let k = 24389.0 / 27.0;
 | 
				
			||||||
 | 
					    let ft3 = ft * ft * ft;
 | 
				
			||||||
 | 
					    if ft3 > e {
 | 
				
			||||||
 | 
					        ft3
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        (116.0 * ft - 16.0) / k
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn lab_f(t: f32) -> f32 {
 | 
				
			||||||
 | 
					    let e = 216.0 / 24389.0;
 | 
				
			||||||
 | 
					    let k = 24389.0 / 27.0;
 | 
				
			||||||
 | 
					    if t > e {
 | 
				
			||||||
 | 
					        t.powf(1.0 / 3.0)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        (k * t + 16.0) / 116.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn y_from_lstar(lstar: f32) -> f32 {
 | 
				
			||||||
 | 
					    100.0 * lab_inv_f((lstar + 16.0) / 116.0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn lstar_from_y(y: f32) -> f32 {
 | 
				
			||||||
 | 
					    lab_f(y / 100.0) * 116.0 - 16.0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn ratio_of_ys(y1: f32, y2: f32) -> f32 {
 | 
				
			||||||
 | 
					    let lighter = y1.max(y2);
 | 
				
			||||||
 | 
					    let darker = y1.min(y2);
 | 
				
			||||||
 | 
					    (lighter + 5.0) / (darker + 5.0)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn ratio_of_tones(a: f32, b: f32) -> f32 {
 | 
				
			||||||
 | 
					    let tone_a = a.clamp(0.0, 100.0);
 | 
				
			||||||
 | 
					    let tone_b = b.clamp(0.0, 100.0);
 | 
				
			||||||
 | 
					    ratio_of_ys(y_from_lstar(tone_a), y_from_lstar(tone_b))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn lighter(tone: f32, ratio: f32) -> f32 {
 | 
				
			||||||
 | 
					    if tone < 0.0 || tone > 100.0 {
 | 
				
			||||||
 | 
					        return -1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let dark_y = y_from_lstar(tone);
 | 
				
			||||||
 | 
					    let light_y = ratio * (dark_y + 5.0) - 5.0;
 | 
				
			||||||
 | 
					    let real_contrast = ratio_of_ys(light_y, dark_y);
 | 
				
			||||||
 | 
					    let delta = (real_contrast - ratio).abs();
 | 
				
			||||||
 | 
					    if real_contrast < ratio && delta > 0.04 {
 | 
				
			||||||
 | 
					        return -1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let return_value = lstar_from_y(light_y) + 0.4;
 | 
				
			||||||
 | 
					    if return_value < 0.0 || return_value > 100.0 {
 | 
				
			||||||
 | 
					        return -1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return_value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn darker(tone: f32, ratio: f32) -> f32 {
 | 
				
			||||||
 | 
					    if tone < 0.0 || tone > 100.0 {
 | 
				
			||||||
 | 
					        return -1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let light_y = y_from_lstar(tone);
 | 
				
			||||||
 | 
					    let dark_y = ((light_y + 5.0) / ratio) - 5.0;
 | 
				
			||||||
 | 
					    let real_contrast = ratio_of_ys(light_y, dark_y);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let delta = (real_contrast - ratio).abs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if real_contrast < ratio && delta > 0.04 {
 | 
				
			||||||
 | 
					        return -1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let return_value = lstar_from_y(dark_y) - 0.4;
 | 
				
			||||||
 | 
					    if return_value < 0.0 || return_value > 100.0 {
 | 
				
			||||||
 | 
					        return -1.0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return_value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn unsafe_lighter(tone: f32, ratio: f32) -> f32 {
 | 
				
			||||||
 | 
					    let safe_lighter = lighter(tone, ratio);
 | 
				
			||||||
 | 
					    if safe_lighter < 0.0 {
 | 
				
			||||||
 | 
					        100.0
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        safe_lighter
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn unsafe_darker(tone: f32, ratio: f32) -> f32 {
 | 
				
			||||||
 | 
					    let safe_darker = darker(tone, ratio);
 | 
				
			||||||
 | 
					    if safe_darker < 0.0 {
 | 
				
			||||||
 | 
					        0.0
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        safe_darker
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					pub struct ContrastCurve {
 | 
				
			||||||
 | 
					    low: f32,
 | 
				
			||||||
 | 
					    normal: f32,
 | 
				
			||||||
 | 
					    medium: f32,
 | 
				
			||||||
 | 
					    high: f32,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn lerp(start: f32, stop: f32, amount: f32) -> f32 {
 | 
				
			||||||
 | 
					    (1.0 - start) * start + amount * stop
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ContrastCurve {
 | 
				
			||||||
 | 
					    pub fn new(low: f32, normal: f32, medium: f32, high: f32) -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            low,
 | 
				
			||||||
 | 
					            normal,
 | 
				
			||||||
 | 
					            medium,
 | 
				
			||||||
 | 
					            high,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(&self, contrast_level: f32) -> f32 {
 | 
				
			||||||
 | 
					        if contrast_level <= -1.0 {
 | 
				
			||||||
 | 
					            self.low
 | 
				
			||||||
 | 
					        } else if contrast_level < 0.0 {
 | 
				
			||||||
 | 
					            lerp(self.low, self.normal, (contrast_level + 1.0) / 1.0)
 | 
				
			||||||
 | 
					        } else if contrast_level < 0.5 {
 | 
				
			||||||
 | 
					            lerp(self.normal, self.medium, contrast_level / 0.5)
 | 
				
			||||||
 | 
					        } else if contrast_level < 1.0 {
 | 
				
			||||||
 | 
					            lerp(self.medium, self.high, (contrast_level - 0.5) / 0.5)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.high
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					use std::rc::Rc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use palette::Lch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::schemes::material_design_3::TonalPalette;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{contrast_curve::ContrastCurve, dynamic_scheme::DynamicScheme};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type TonalPaletteGenerator = Box<dyn Fn(&DynamicScheme) -> TonalPalette>;
 | 
				
			||||||
 | 
					pub type ToneSearcher = Box<dyn Fn(&DynamicScheme) -> f32>;
 | 
				
			||||||
 | 
					pub type DynamicColorSearcher = Box<dyn Fn(&DynamicScheme) -> Rc<DynamicColor>>;
 | 
				
			||||||
 | 
					pub type ToneDeltaPairGenerator = Box<dyn Fn(&DynamicScheme) -> ToneDeltaPair>;
 | 
				
			||||||
 | 
					pub type CustomPaletteGenerator = Box<dyn Fn(&Lch) -> TonalPalette>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq)]
 | 
				
			||||||
 | 
					#[allow(dead_code)]
 | 
				
			||||||
 | 
					pub enum TonePolarity {
 | 
				
			||||||
 | 
					    Darker,
 | 
				
			||||||
 | 
					    Lighter,
 | 
				
			||||||
 | 
					    Nearer,
 | 
				
			||||||
 | 
					    Farther,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct DynamicColor {
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    palette: TonalPaletteGenerator,
 | 
				
			||||||
 | 
					    tone: ToneSearcher,
 | 
				
			||||||
 | 
					    is_background: Option<bool>,
 | 
				
			||||||
 | 
					    background: Option<DynamicColorSearcher>,
 | 
				
			||||||
 | 
					    secondary_background: Option<DynamicColorSearcher>,
 | 
				
			||||||
 | 
					    contrast_curve: Option<ContrastCurve>,
 | 
				
			||||||
 | 
					    tone_delta_pairs: Option<ToneDeltaPairGenerator>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct ToneDeltaPair {
 | 
				
			||||||
 | 
					    pub role_a: Rc<DynamicColor>,
 | 
				
			||||||
 | 
					    pub role_b: Rc<DynamicColor>,
 | 
				
			||||||
 | 
					    pub delta: f32,
 | 
				
			||||||
 | 
					    pub polarity: TonePolarity,
 | 
				
			||||||
 | 
					    pub togather: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl DynamicColor {
 | 
				
			||||||
 | 
					    pub fn new(
 | 
				
			||||||
 | 
					        name: Option<&str>,
 | 
				
			||||||
 | 
					        palette: TonalPaletteGenerator,
 | 
				
			||||||
 | 
					        tone: ToneSearcher,
 | 
				
			||||||
 | 
					        is_background: Option<bool>,
 | 
				
			||||||
 | 
					        background: Option<DynamicColorSearcher>,
 | 
				
			||||||
 | 
					        secondary_background: Option<DynamicColorSearcher>,
 | 
				
			||||||
 | 
					        contrast_curve: Option<ContrastCurve>,
 | 
				
			||||||
 | 
					        tone_delta_pairs: Option<ToneDeltaPairGenerator>,
 | 
				
			||||||
 | 
					    ) -> Self {
 | 
				
			||||||
 | 
					        DynamicColor {
 | 
				
			||||||
 | 
					            name: name.unwrap_or("").to_string(),
 | 
				
			||||||
 | 
					            palette,
 | 
				
			||||||
 | 
					            tone,
 | 
				
			||||||
 | 
					            is_background: is_background.or(Some(false)),
 | 
				
			||||||
 | 
					            background,
 | 
				
			||||||
 | 
					            secondary_background,
 | 
				
			||||||
 | 
					            contrast_curve,
 | 
				
			||||||
 | 
					            tone_delta_pairs,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn tone(&self, scheme: &DynamicScheme) -> f32 {
 | 
				
			||||||
 | 
					        (self.tone)(scheme)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_lch(&self, scheme: &DynamicScheme) -> Lch {
 | 
				
			||||||
 | 
					        let tone = self.get_tone(scheme);
 | 
				
			||||||
 | 
					        (self.palette)(scheme).tone(tone)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_tone(&self, scheme: &DynamicScheme) -> f32 {
 | 
				
			||||||
 | 
					        let decreasing_contrast = scheme.contrast_level < 0.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(pair_generator) = &self.tone_delta_pairs {
 | 
				
			||||||
 | 
					            let tone_delta = pair_generator(scheme);
 | 
				
			||||||
 | 
					            let bg = (self.background.as_ref().unwrap())(scheme);
 | 
				
			||||||
 | 
					            let bg_tone = bg.get_tone(scheme);
 | 
				
			||||||
 | 
					            let is_nearer = tone_delta.polarity == TonePolarity::Nearer
 | 
				
			||||||
 | 
					                || (tone_delta.polarity == TonePolarity::Lighter && !scheme.is_dark)
 | 
				
			||||||
 | 
					                || (tone_delta.polarity == TonePolarity::Darker && scheme.is_dark);
 | 
				
			||||||
 | 
					            let (nearer, farther) = if is_nearer {
 | 
				
			||||||
 | 
					                (&tone_delta.role_a, &tone_delta.role_b)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                (&tone_delta.role_b, &tone_delta.role_a)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					            let expansion_factor = if scheme.is_dark { 1.0 } else { -1.0 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let n_contrast = (nearer.contrast_curve.as_ref().unwrap()).get(scheme.contrast_level);
 | 
				
			||||||
 | 
					            let f_contrast = (farther.contrast_curve.as_ref().unwrap()).get(scheme.contrast_level);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let n_initial_tone = nearer.tone(scheme);
 | 
				
			||||||
 | 
					            let mut n_tone =
 | 
				
			||||||
 | 
					                if super::contrast::ratio_of_tones(bg_tone, n_initial_tone) >= n_contrast {
 | 
				
			||||||
 | 
					                    n_initial_tone
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    foreground_tone(bg_tone, n_contrast)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            let f_initial_tone = farther.tone(scheme);
 | 
				
			||||||
 | 
					            let mut f_tone =
 | 
				
			||||||
 | 
					                if super::contrast::ratio_of_tones(bg_tone, f_initial_tone) >= f_contrast {
 | 
				
			||||||
 | 
					                    f_initial_tone
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    foreground_tone(bg_tone, f_contrast)
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if decreasing_contrast {
 | 
				
			||||||
 | 
					                n_tone = foreground_tone(bg_tone, n_contrast);
 | 
				
			||||||
 | 
					                f_tone = foreground_tone(bg_tone, f_contrast);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (f_tone - n_tone) * expansion_factor < tone_delta.delta {
 | 
				
			||||||
 | 
					                f_tone = (n_tone + tone_delta.delta * expansion_factor).clamp(0.0, 100.0);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if n_tone >= 50.0 && n_tone < 60.0 {
 | 
				
			||||||
 | 
					                if expansion_factor > 0.0 {
 | 
				
			||||||
 | 
					                    n_tone = 60.0;
 | 
				
			||||||
 | 
					                    f_tone = (n_tone + tone_delta.delta * expansion_factor).max(f_tone);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    n_tone = 49.0;
 | 
				
			||||||
 | 
					                    f_tone = (n_tone + tone_delta.delta * expansion_factor).min(f_tone);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else if f_tone >= 50.0 && f_tone < 60.0 {
 | 
				
			||||||
 | 
					                if tone_delta.togather {
 | 
				
			||||||
 | 
					                    if expansion_factor > 0.0 {
 | 
				
			||||||
 | 
					                        n_tone = 60.0;
 | 
				
			||||||
 | 
					                        f_tone = (n_tone + tone_delta.delta * expansion_factor).max(f_tone);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        n_tone = 49.0;
 | 
				
			||||||
 | 
					                        f_tone = (n_tone + tone_delta.delta * expansion_factor).min(f_tone);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if expansion_factor > 0.0 {
 | 
				
			||||||
 | 
					                        f_tone = 60.0;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        f_tone = 49.0;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.name.eq_ignore_ascii_case(&nearer.name) {
 | 
				
			||||||
 | 
					                n_tone
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                f_tone
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let mut result = (self.tone)(scheme);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.background.is_none() {
 | 
				
			||||||
 | 
					                return result;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let bg_tone = (self.background.as_ref().unwrap())(scheme).get_tone(scheme);
 | 
				
			||||||
 | 
					            let desired_ratio = (self.contrast_curve.as_ref().unwrap()).get(scheme.contrast_level);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if super::contrast::ratio_of_tones(bg_tone, result) < desired_ratio {
 | 
				
			||||||
 | 
					                result = foreground_tone(bg_tone, desired_ratio);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if decreasing_contrast {
 | 
				
			||||||
 | 
					                result = foreground_tone(bg_tone, desired_ratio);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if self.is_background.unwrap_or(false) && result >= 50.0 && result < 60.0 {
 | 
				
			||||||
 | 
					                if super::contrast::ratio_of_tones(49.0, bg_tone) >= desired_ratio {
 | 
				
			||||||
 | 
					                    result = 49.0;
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    result = 60.0;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Some(secondary_background) = &self.secondary_background {
 | 
				
			||||||
 | 
					                let (bg_tone_1, bg_tone_2) = (
 | 
				
			||||||
 | 
					                    (self.background.as_ref().unwrap())(scheme).get_tone(scheme),
 | 
				
			||||||
 | 
					                    secondary_background(scheme).get_tone(scheme),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                let (upper, lower) = (bg_tone_1.max(bg_tone_2), bg_tone_1.min(bg_tone_2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if super::contrast::ratio_of_tones(upper, result) >= desired_ratio
 | 
				
			||||||
 | 
					                    && super::contrast::ratio_of_tones(lower, result) >= desired_ratio
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return result;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let light_option = super::contrast::lighter(upper, desired_ratio);
 | 
				
			||||||
 | 
					                let dark_option = super::contrast::darker(lower, desired_ratio);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let mut availables = vec![];
 | 
				
			||||||
 | 
					                if light_option != -1.0 {
 | 
				
			||||||
 | 
					                    availables.push(light_option);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if dark_option != -1.0 {
 | 
				
			||||||
 | 
					                    availables.push(dark_option);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if prefer_light_foreground(bg_tone_1) || prefer_light_foreground(bg_tone_2) {
 | 
				
			||||||
 | 
					                    return if light_option < 0.0 {
 | 
				
			||||||
 | 
					                        100.0
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        light_option
 | 
				
			||||||
 | 
					                    };
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if availables.len() == 1 {
 | 
				
			||||||
 | 
					                    return availables[0];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return if dark_option < 0.0 { 0.0 } else { dark_option };
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn prefer_light_foreground(tone: f32) -> bool {
 | 
				
			||||||
 | 
					    tone.round() < 60.0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn foreground_tone(background_tone: f32, ratio: f32) -> f32 {
 | 
				
			||||||
 | 
					    let lighter_tone = super::contrast::unsafe_lighter(background_tone, ratio);
 | 
				
			||||||
 | 
					    let darker_tone = super::contrast::unsafe_darker(background_tone, ratio);
 | 
				
			||||||
 | 
					    let lighter_ratio = super::contrast::ratio_of_tones(lighter_tone, background_tone);
 | 
				
			||||||
 | 
					    let darker_ratio = super::contrast::ratio_of_tones(darker_tone, background_tone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if prefer_light_foreground(background_tone) {
 | 
				
			||||||
 | 
					        let difference = (lighter_ratio - darker_ratio).abs() < 0.1
 | 
				
			||||||
 | 
					            && lighter_ratio < ratio
 | 
				
			||||||
 | 
					            && darker_ratio < ratio;
 | 
				
			||||||
 | 
					        if lighter_ratio >= ratio || lighter_ratio >= darker_ratio || difference {
 | 
				
			||||||
 | 
					            lighter_tone
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            darker_tone
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        if darker_ratio >= ratio || darker_ratio >= lighter_ratio {
 | 
				
			||||||
 | 
					            darker_tone
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            lighter_tone
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use palette::Lch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::schemes::material_design_3::TonalPalette;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::constants::Variant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[allow(dead_code)]
 | 
				
			||||||
 | 
					pub struct DynamicScheme {
 | 
				
			||||||
 | 
					    pub source_color: Lch,
 | 
				
			||||||
 | 
					    pub error_palette: TonalPalette,
 | 
				
			||||||
 | 
					    pub contrast_level: f32,
 | 
				
			||||||
 | 
					    pub variant: Variant,
 | 
				
			||||||
 | 
					    pub is_dark: bool,
 | 
				
			||||||
 | 
					    pub harmonize_customs: bool,
 | 
				
			||||||
 | 
					    pub primary_palette: TonalPalette,
 | 
				
			||||||
 | 
					    pub secondary_palette: TonalPalette,
 | 
				
			||||||
 | 
					    pub tertiary_palette: TonalPalette,
 | 
				
			||||||
 | 
					    pub neutral_palette: TonalPalette,
 | 
				
			||||||
 | 
					    pub neutral_variant_palette: TonalPalette,
 | 
				
			||||||
 | 
					    pub custom_palettes: HashMap<String, TonalPalette>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl DynamicScheme {
 | 
				
			||||||
 | 
					    pub fn new(
 | 
				
			||||||
 | 
					        source_color: Lch,
 | 
				
			||||||
 | 
					        error_color: Option<Lch>,
 | 
				
			||||||
 | 
					        custom_colors: HashMap<String, Lch>,
 | 
				
			||||||
 | 
					        variant: Variant,
 | 
				
			||||||
 | 
					        contrast_level: f32,
 | 
				
			||||||
 | 
					        is_dark: bool,
 | 
				
			||||||
 | 
					        harmonize_customs: bool,
 | 
				
			||||||
 | 
					    ) -> Self {
 | 
				
			||||||
 | 
					        let (
 | 
				
			||||||
 | 
					            primary_palette,
 | 
				
			||||||
 | 
					            secondary_palette,
 | 
				
			||||||
 | 
					            tertiary_palette,
 | 
				
			||||||
 | 
					            neutral_palette,
 | 
				
			||||||
 | 
					            neutral_variant_palette,
 | 
				
			||||||
 | 
					            custom_generator,
 | 
				
			||||||
 | 
					        ) = variant.build_palette(source_color, harmonize_customs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let custom_palettes = custom_colors
 | 
				
			||||||
 | 
					            .into_iter()
 | 
				
			||||||
 | 
					            .map(|(name, color)| (name, custom_generator(&color)))
 | 
				
			||||||
 | 
					            .collect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DynamicScheme {
 | 
				
			||||||
 | 
					            source_color,
 | 
				
			||||||
 | 
					            error_palette: error_color
 | 
				
			||||||
 | 
					                .map(|error_color| {
 | 
				
			||||||
 | 
					                    TonalPalette::from_hue_and_chroma(
 | 
				
			||||||
 | 
					                        error_color.hue.into_positive_degrees(),
 | 
				
			||||||
 | 
					                        error_color.chroma,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					                .unwrap_or_else(|| TonalPalette::from_hue_and_chroma(25.0, 48.0)),
 | 
				
			||||||
 | 
					            contrast_level,
 | 
				
			||||||
 | 
					            variant,
 | 
				
			||||||
 | 
					            is_dark,
 | 
				
			||||||
 | 
					            harmonize_customs,
 | 
				
			||||||
 | 
					            primary_palette,
 | 
				
			||||||
 | 
					            secondary_palette,
 | 
				
			||||||
 | 
					            tertiary_palette,
 | 
				
			||||||
 | 
					            neutral_palette,
 | 
				
			||||||
 | 
					            neutral_variant_palette,
 | 
				
			||||||
 | 
					            custom_palettes,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,917 @@
 | 
				
			|||||||
 | 
					use std::{cell::LazyCell, rc::Rc};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::cond;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{
 | 
				
			||||||
 | 
					    constants::{fix_disliked, Variant},
 | 
				
			||||||
 | 
					    contrast_curve::ContrastCurve,
 | 
				
			||||||
 | 
					    dynamic_color::{foreground_tone, DynamicColor, ToneDeltaPair, TonePolarity},
 | 
				
			||||||
 | 
					    dynamic_scheme::DynamicScheme,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					macro_rules! dynamic_gen {
 | 
				
			||||||
 | 
					    ($variable: ident, $name: ident, $palette: expr, $tone: expr) => {
 | 
				
			||||||
 | 
					        pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
 | 
				
			||||||
 | 
					            Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					                Some(stringify!($name)),
 | 
				
			||||||
 | 
					                Box::new($palette),
 | 
				
			||||||
 | 
					                Box::new($tone),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ($variable: ident, $name: ident, $palette: expr, $tone: expr, $is_background: expr) => {
 | 
				
			||||||
 | 
					        pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
 | 
				
			||||||
 | 
					            Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					                Some(stringify!($name)),
 | 
				
			||||||
 | 
					                Box::new($palette),
 | 
				
			||||||
 | 
					                Box::new($tone),
 | 
				
			||||||
 | 
					                Some($is_background),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ($variable: ident, $name: ident, $palette: expr, $tone: expr, $background: expr, $contrast_curve: expr) => {
 | 
				
			||||||
 | 
					        pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
 | 
				
			||||||
 | 
					            Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					                Some(stringify!($name)),
 | 
				
			||||||
 | 
					                Box::new($palette),
 | 
				
			||||||
 | 
					                Box::new($tone),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                Some(Box::new($background)),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					                Some($contrast_curve),
 | 
				
			||||||
 | 
					                None,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    ($variable: ident, $name: ident, $palette: expr, $tone: expr, $is_background: expr, $background: expr, $secondary_background: expr, $contrast_curve: expr, $tone_delta_pairs: expr) => {
 | 
				
			||||||
 | 
					        pub const $variable: LazyCell<Rc<DynamicColor>> = LazyCell::new(|| {
 | 
				
			||||||
 | 
					            Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					                Some(stringify!($name)),
 | 
				
			||||||
 | 
					                Box::new($palette),
 | 
				
			||||||
 | 
					                Box::new($tone),
 | 
				
			||||||
 | 
					                $is_background,
 | 
				
			||||||
 | 
					                $background,
 | 
				
			||||||
 | 
					                $secondary_background,
 | 
				
			||||||
 | 
					                $contrast_curve,
 | 
				
			||||||
 | 
					                $tone_delta_pairs,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn is_fidelity(scheme: &DynamicScheme) -> bool {
 | 
				
			||||||
 | 
					    scheme.variant == Variant::Fidelity || scheme.variant == Variant::Content
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn is_monochrome(scheme: &DynamicScheme) -> bool {
 | 
				
			||||||
 | 
					    scheme.variant == Variant::Monochrome
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn highest_surface(s: &DynamicScheme) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    cond!(s.is_dark, Rc::clone(&SURFACE_BRIGHT), Rc::clone(&SURFACE))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE,
 | 
				
			||||||
 | 
					    surface,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 6.0, 98.0),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_DIM,
 | 
				
			||||||
 | 
					    surface_dim,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        6.0,
 | 
				
			||||||
 | 
					        ContrastCurve::new(87.0, 87.0, 80.0, 75.0).get(s.contrast_level)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_BRIGHT,
 | 
				
			||||||
 | 
					    surface_bright,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        ContrastCurve::new(24.0, 24.0, 29.0, 34.0).get(s.contrast_level),
 | 
				
			||||||
 | 
					        98.0
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_CONTAINER_LOWEST,
 | 
				
			||||||
 | 
					    surface_container_lowest,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        ContrastCurve::new(4.0, 4.0, 2.0, 0.0).get(s.contrast_level),
 | 
				
			||||||
 | 
					        100.0
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_CONTAINER_LOW,
 | 
				
			||||||
 | 
					    surface_container_low,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        ContrastCurve::new(10.0, 10.0, 11.0, 12.0).get(s.contrast_level),
 | 
				
			||||||
 | 
					        ContrastCurve::new(96.0, 96.0, 96.0, 95.0).get(s.contrast_level)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_CONTAINER,
 | 
				
			||||||
 | 
					    surface_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        ContrastCurve::new(12.0, 12.0, 16.0, 20.0).get(s.contrast_level),
 | 
				
			||||||
 | 
					        ContrastCurve::new(94.0, 94.0, 92.0, 90.0).get(s.contrast_level)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_CONTAINER_HIGH,
 | 
				
			||||||
 | 
					    surface_container_high,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        ContrastCurve::new(17.0, 17.0, 21.0, 25.0).get(s.contrast_level),
 | 
				
			||||||
 | 
					        ContrastCurve::new(92.0, 92.0, 88.0, 85.0).get(s.contrast_level)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_CONTAINER_HIGHEST,
 | 
				
			||||||
 | 
					    surface_container_highest,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(
 | 
				
			||||||
 | 
					        s.is_dark,
 | 
				
			||||||
 | 
					        ContrastCurve::new(22.0, 22.0, 26.0, 30.0).get(s.contrast_level),
 | 
				
			||||||
 | 
					        ContrastCurve::new(90.0, 90.0, 84.0, 80.0).get(s.contrast_level)
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_SURFACE,
 | 
				
			||||||
 | 
					    on_surface,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 90.0, 10.0),
 | 
				
			||||||
 | 
					    highest_surface,
 | 
				
			||||||
 | 
					    ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SURFACE_VARIANT,
 | 
				
			||||||
 | 
					    surface_variant,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_variant_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 30.0, 90.0),
 | 
				
			||||||
 | 
					    true
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_SURFACE_VARIANT,
 | 
				
			||||||
 | 
					    on_surface_variant,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_variant_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 80.0, 30.0),
 | 
				
			||||||
 | 
					    highest_surface,
 | 
				
			||||||
 | 
					    ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    INVERSE_SURFACE,
 | 
				
			||||||
 | 
					    inverse_surface,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 20.0, 95.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    INVERSE_ON_SURFACE,
 | 
				
			||||||
 | 
					    inverse_on_surface,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 20.0, 95.0),
 | 
				
			||||||
 | 
					    |_| Rc::clone(&INVERSE_SURFACE),
 | 
				
			||||||
 | 
					    ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    OUTLINE,
 | 
				
			||||||
 | 
					    outline,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_variant_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 60.0, 50.0),
 | 
				
			||||||
 | 
					    highest_surface,
 | 
				
			||||||
 | 
					    ContrastCurve::new(1.5, 3.0, 4.5, 7.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    OUTLINE_VARIANT,
 | 
				
			||||||
 | 
					    outline_variant,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_variant_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 30.0, 80.0),
 | 
				
			||||||
 | 
					    highest_surface,
 | 
				
			||||||
 | 
					    ContrastCurve::new(1.0, 1.0, 3.0, 4.5)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SHADOW,
 | 
				
			||||||
 | 
					    shadow,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |_| 0.0
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SCRIM,
 | 
				
			||||||
 | 
					    scrim,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.neutral_palette.clone(),
 | 
				
			||||||
 | 
					    |_| { 0.0 }
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    PRIMARY,
 | 
				
			||||||
 | 
					    primary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 100.0, 0.0)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 80.0, 40.0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&PRIMARY_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&PRIMARY),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_PRIMARY,
 | 
				
			||||||
 | 
					    on_primary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 10.0, 90.0)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 20.0, 100.0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&PRIMARY),
 | 
				
			||||||
 | 
					    ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    PRIMARY_CONTAINER,
 | 
				
			||||||
 | 
					    primary_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_fidelity(s) {
 | 
				
			||||||
 | 
					            return s.source_color.l;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 85.0, 25.0)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 30.0, 90.0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&PRIMARY_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&PRIMARY),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_PRIMARY_CONTAINER,
 | 
				
			||||||
 | 
					    on_primary_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_fidelity(s) {
 | 
				
			||||||
 | 
					            return foreground_tone(Rc::clone(&PRIMARY_CONTAINER).get_tone(s), 4.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 0.0, 100.0)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 90.0, 30.0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&PRIMARY_CONTAINER),
 | 
				
			||||||
 | 
					    ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    INVERSE_PRIMARY,
 | 
				
			||||||
 | 
					    inverse_primary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 40.0, 80.0),
 | 
				
			||||||
 | 
					    |_| Rc::clone(&INVERSE_SURFACE),
 | 
				
			||||||
 | 
					    ContrastCurve::new(3.0, 4.5, 7.0, 7.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SECONDARY,
 | 
				
			||||||
 | 
					    secondary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 80.0, 40.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_SECONDARY,
 | 
				
			||||||
 | 
					    on_secondary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 10.0, 100.0)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 20.0, 100.0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&SECONDARY),
 | 
				
			||||||
 | 
					    ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SECONDARY_CONTAINER,
 | 
				
			||||||
 | 
					    secondary_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        let initial_tone = cond!(s.is_dark, 30.0, 90.0);
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 30.0, 85.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        initial_tone
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&SECONDARY_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&SECONDARY),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_SECONDARY_CONTAINER,
 | 
				
			||||||
 | 
					    on_secondary_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 90.0, 10.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if !is_fidelity(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 90.0, 30.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        foreground_tone(Rc::clone(&SECONDARY_CONTAINER).get_tone(s), 4.5)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&SECONDARY_CONTAINER),
 | 
				
			||||||
 | 
					    ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    TERTIARY,
 | 
				
			||||||
 | 
					    tertiary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 90.0, 25.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        cond!(s.is_dark, 80.0, 40.0)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&TERTIARY_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&TERTIARY),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_TERTIARY,
 | 
				
			||||||
 | 
					    on_tertiary,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 10.0, 90.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        cond!(s.is_dark, 20.0, 100.0)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&TERTIARY),
 | 
				
			||||||
 | 
					    ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    TERTIARY_CONTAINER,
 | 
				
			||||||
 | 
					    tertiary_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 60.0, 49.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if !is_fidelity(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 30.0, 90.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        let proposed_lch = s.tertiary_palette.tone(s.source_color.l);
 | 
				
			||||||
 | 
					        fix_disliked(&proposed_lch).l
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&TERTIARY_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&TERTIARY),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_TERTIARY_CONTAINER,
 | 
				
			||||||
 | 
					    on_tertiary_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 0.0, 100.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if !is_fidelity(s) {
 | 
				
			||||||
 | 
					            return cond!(s.is_dark, 90.0, 30.0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        foreground_tone(Rc::clone(&TERTIARY_CONTAINER).get_tone(s), 4.5)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&TERTIARY_CONTAINER),
 | 
				
			||||||
 | 
					    ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ERROR,
 | 
				
			||||||
 | 
					    error,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.error_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 80.0, 40.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&ERROR_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&ERROR),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_ERROR,
 | 
				
			||||||
 | 
					    on_error,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.error_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 20.0, 100.0),
 | 
				
			||||||
 | 
					    |_| Rc::clone(&ERROR),
 | 
				
			||||||
 | 
					    ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ERROR_CONTAINER,
 | 
				
			||||||
 | 
					    error_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.error_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(s.is_dark, 30.0, 90.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&ERROR_CONTAINER),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&ERROR),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					        togather: false,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_ERROR_CONTAINER,
 | 
				
			||||||
 | 
					    on_error_container,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.error_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| {
 | 
				
			||||||
 | 
					        if is_monochrome(s) {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 90.0, 10.0)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            cond!(s.is_dark, 90.0, 30.0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    |_| Rc::clone(&ERROR_CONTAINER),
 | 
				
			||||||
 | 
					    ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    PRIMARY_FIXED,
 | 
				
			||||||
 | 
					    primary_fixed,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 40.0, 90.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&PRIMARY_FIXED),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&PRIMARY_FIXED_DIM),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					        togather: true,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    PRIMARY_FIXED_DIM,
 | 
				
			||||||
 | 
					    primary_fixed_dim,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 30.0, 80.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&PRIMARY_FIXED),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&PRIMARY_FIXED_DIM),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					        togather: true,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_PRIMARY_FIXED,
 | 
				
			||||||
 | 
					    on_primary_fixed,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 100.0, 10.0),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED_DIM))),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED))),
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_PRIMARY_FIXED_VARIANT,
 | 
				
			||||||
 | 
					    on_primary_fixed_variant,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.primary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 90.0, 30.0),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED_DIM))),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&PRIMARY_FIXED))),
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SECONDARY_FIXED,
 | 
				
			||||||
 | 
					    secondary_fixed,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 80.0, 90.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&SECONDARY_FIXED),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&SECONDARY_FIXED_DIM),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					        togather: true,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    SECONDARY_FIXED_DIM,
 | 
				
			||||||
 | 
					    secondary_fixed_dim,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 70.0, 80.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&SECONDARY_FIXED),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&SECONDARY_FIXED_DIM),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					        togather: true,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_SECONDARY_FIXED,
 | 
				
			||||||
 | 
					    on_secondary_fixed,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |_| 10.0,
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED_DIM))),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED))),
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_SECONDARY_FIXED_VARIANT,
 | 
				
			||||||
 | 
					    on_secondary_fixed_variant,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.secondary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 25.0, 30.0),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED_DIM))),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&SECONDARY_FIXED))),
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    TERTIARY_FIXED,
 | 
				
			||||||
 | 
					    tertiary_fixed,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 40.0, 90.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&TERTIARY_FIXED),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&TERTIARY_FIXED_DIM),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					        togather: true,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    TERTIARY_FIXED_DIM,
 | 
				
			||||||
 | 
					    tertiary_fixed_dim,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 30.0, 80.0),
 | 
				
			||||||
 | 
					    Some(true),
 | 
				
			||||||
 | 
					    Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| ToneDeltaPair {
 | 
				
			||||||
 | 
					        role_a: Rc::clone(&TERTIARY_FIXED),
 | 
				
			||||||
 | 
					        role_b: Rc::clone(&TERTIARY_FIXED_DIM),
 | 
				
			||||||
 | 
					        delta: 10.0,
 | 
				
			||||||
 | 
					        polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					        togather: true,
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_TERTIARY_FIXED,
 | 
				
			||||||
 | 
					    on_tertiary_fixed,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 100.0, 10.0),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED_DIM))),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED))),
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					dynamic_gen!(
 | 
				
			||||||
 | 
					    ON_TERTIARY_FIXED_VARIANT,
 | 
				
			||||||
 | 
					    on_tertiary_fixed_variant,
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| s.tertiary_palette.clone(),
 | 
				
			||||||
 | 
					    |s: &DynamicScheme| cond!(is_monochrome(s), 90.0, 30.0),
 | 
				
			||||||
 | 
					    None,
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED_DIM))),
 | 
				
			||||||
 | 
					    Some(Box::new(|_| Rc::clone(&TERTIARY_FIXED))),
 | 
				
			||||||
 | 
					    Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
 | 
				
			||||||
 | 
					    None
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn custom(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("{}", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s: &DynamicScheme| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s: &DynamicScheme| {
 | 
				
			||||||
 | 
					            if is_monochrome(s) {
 | 
				
			||||||
 | 
					                cond!(s.is_dark, 100.0, 0.0)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                cond!(s.is_dark, 80.0, 40.0)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        Some(true),
 | 
				
			||||||
 | 
					        Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(3.0, 4.5, 7.0, 7.0)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| ToneDeltaPair {
 | 
				
			||||||
 | 
					                role_a: custom_container(String::from(&name)),
 | 
				
			||||||
 | 
					                role_b: custom(String::from(&name)),
 | 
				
			||||||
 | 
					                delta: 10.0,
 | 
				
			||||||
 | 
					                polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					                togather: false,
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn on_custom(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("on_{}", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| {
 | 
				
			||||||
 | 
					                let custom_palette = s.custom_palettes.get(&name).unwrap().clone();
 | 
				
			||||||
 | 
					                custom_palette
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s: &DynamicScheme| {
 | 
				
			||||||
 | 
					            if is_monochrome(s) {
 | 
				
			||||||
 | 
					                cond!(s.is_dark, 10.0, 90.0)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                cond!(s.is_dark, 20.0, 100.0)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| custom(String::from(&name))))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn custom_container(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("{}_container", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s| {
 | 
				
			||||||
 | 
					            if is_fidelity(s) {
 | 
				
			||||||
 | 
					                return s.source_color.l;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if is_monochrome(s) {
 | 
				
			||||||
 | 
					                cond!(s.is_dark, 85.0, 25.0)
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                cond!(s.is_dark, 30.0, 90.0)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        Some(true),
 | 
				
			||||||
 | 
					        Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| ToneDeltaPair {
 | 
				
			||||||
 | 
					                role_a: custom_container(String::from(&name)),
 | 
				
			||||||
 | 
					                role_b: custom(String::from(&name)),
 | 
				
			||||||
 | 
					                delta: 10.0,
 | 
				
			||||||
 | 
					                polarity: TonePolarity::Nearer,
 | 
				
			||||||
 | 
					                togather: false,
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn on_custom_container(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("on_{}_container", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| {
 | 
				
			||||||
 | 
					                let custom_palette = s.custom_palettes.get(&name).unwrap().clone();
 | 
				
			||||||
 | 
					                custom_palette
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| {
 | 
				
			||||||
 | 
					                if is_fidelity(s) {
 | 
				
			||||||
 | 
					                    return foreground_tone(custom_container(String::from(&name)).get_tone(s), 4.5);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if is_monochrome(s) {
 | 
				
			||||||
 | 
					                    cond!(s.is_dark, 0.0, 100.0)
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    cond!(s.is_dark, 90.0, 30.0)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| custom_container(String::from(&name))))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn inverse_custom(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("inverse_{}", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s| cond!(s.is_dark, 20.0, 95.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(Box::new(|_| Rc::clone(&INVERSE_SURFACE))),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn custom_fixed(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("{}_fixed", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s| cond!(is_monochrome(s), 40.0, 90.0)),
 | 
				
			||||||
 | 
					        Some(true),
 | 
				
			||||||
 | 
					        Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| ToneDeltaPair {
 | 
				
			||||||
 | 
					                role_a: custom_fixed(String::from(&name)),
 | 
				
			||||||
 | 
					                role_b: custom_fixed_dim(String::from(&name)),
 | 
				
			||||||
 | 
					                delta: 10.0,
 | 
				
			||||||
 | 
					                polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					                togather: true,
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn custom_fixed_dim(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("{}_fixed_dim", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s| cond!(is_monochrome(s), 30.0, 80.0)),
 | 
				
			||||||
 | 
					        Some(true),
 | 
				
			||||||
 | 
					        Some(Box::new(highest_surface)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(1.0, 1.0, 3.0, 4.5)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| ToneDeltaPair {
 | 
				
			||||||
 | 
					                role_a: custom_fixed(String::from(&name)),
 | 
				
			||||||
 | 
					                role_b: custom_fixed_dim(String::from(&name)),
 | 
				
			||||||
 | 
					                delta: 10.0,
 | 
				
			||||||
 | 
					                polarity: TonePolarity::Lighter,
 | 
				
			||||||
 | 
					                togather: true,
 | 
				
			||||||
 | 
					            }))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn on_custom_fixed(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("on_{}_fixed", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s| cond!(is_monochrome(s), 100.0, 10.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| custom_fixed_dim(String::from(&name))))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Some(Box::new(move |_| custom_fixed(String::from(&name)))),
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn on_custom_fixed_variant(name: String) -> Rc<DynamicColor> {
 | 
				
			||||||
 | 
					    Rc::new(DynamicColor::new(
 | 
				
			||||||
 | 
					        Some(&format!("on_{}_fixed_variant", name)),
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Box::new(move |s| s.custom_palettes.get(&name).unwrap().clone())
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Box::new(|s| cond!(is_monochrome(s), 90.0, 30.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let name = name.clone();
 | 
				
			||||||
 | 
					            Some(Box::new(move |_| custom_fixed_dim(String::from(&name))))
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        Some(Box::new(move |_| custom_fixed(String::from(&name)))),
 | 
				
			||||||
 | 
					        Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										221
									
								
								color-module/src/schemes/material_design_3_dynamic/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								color-module/src/schemes/material_design_3_dynamic/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,221 @@
 | 
				
			|||||||
 | 
					use std::{collections::HashMap, str::FromStr};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use dynamic_scheme::DynamicScheme;
 | 
				
			||||||
 | 
					use material_colors::*;
 | 
				
			||||||
 | 
					use palette::{IntoColor, Lch, Srgb};
 | 
				
			||||||
 | 
					use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{cond, convert::map_lch_to_srgb_hex, errors};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::material_design_3::{M3BaselineColors, M3ColorSet, M3PaletteSwatch, M3SurfaceSet};
 | 
				
			||||||
 | 
					pub use constants::Variant;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod constants;
 | 
				
			||||||
 | 
					mod contrast;
 | 
				
			||||||
 | 
					mod contrast_curve;
 | 
				
			||||||
 | 
					mod dynamic_color;
 | 
				
			||||||
 | 
					mod dynamic_scheme;
 | 
				
			||||||
 | 
					mod material_colors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[wasm_bindgen]
 | 
				
			||||||
 | 
					pub fn material_design_3_dynamic_variant() -> Result<JsValue, String> {
 | 
				
			||||||
 | 
					    let variants = enum_iterator::all::<constants::Variant>()
 | 
				
			||||||
 | 
					        .map(|variant| {
 | 
				
			||||||
 | 
					            serde_json::json!({
 | 
				
			||||||
 | 
					                "label": variant.label(),
 | 
				
			||||||
 | 
					                "value": variant as u8,
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .collect::<Vec<_>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(serde_wasm_bindgen::to_value(&variants).map_err(|e| e.to_string())?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_primary_color_set(scheme: &DynamicScheme) -> M3ColorSet {
 | 
				
			||||||
 | 
					    M3ColorSet {
 | 
				
			||||||
 | 
					        root: map_lch_to_srgb_hex(&PRIMARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root: map_lch_to_srgb_hex(&ON_PRIMARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container: map_lch_to_srgb_hex(&PRIMARY_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_container: map_lch_to_srgb_hex(&ON_PRIMARY_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed: map_lch_to_srgb_hex(&PRIMARY_FIXED.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_fixed: map_lch_to_srgb_hex(&ON_PRIMARY_FIXED.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_variant: map_lch_to_srgb_hex(&ON_PRIMARY_FIXED_VARIANT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_dim: map_lch_to_srgb_hex(&PRIMARY_FIXED_DIM.get_lch(scheme)),
 | 
				
			||||||
 | 
					        inverse: map_lch_to_srgb_hex(&INVERSE_PRIMARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_secondary_color_set(scheme: &DynamicScheme) -> M3ColorSet {
 | 
				
			||||||
 | 
					    M3ColorSet {
 | 
				
			||||||
 | 
					        root: map_lch_to_srgb_hex(&SECONDARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root: map_lch_to_srgb_hex(&ON_SECONDARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container: map_lch_to_srgb_hex(&SECONDARY_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_container: map_lch_to_srgb_hex(&ON_SECONDARY_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed: map_lch_to_srgb_hex(&SECONDARY_FIXED.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_fixed: map_lch_to_srgb_hex(&ON_SECONDARY_FIXED.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_variant: map_lch_to_srgb_hex(&ON_SECONDARY_FIXED_VARIANT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_dim: map_lch_to_srgb_hex(&SECONDARY_FIXED_DIM.get_lch(scheme)),
 | 
				
			||||||
 | 
					        ..cond!(
 | 
				
			||||||
 | 
					            scheme.is_dark,
 | 
				
			||||||
 | 
					            M3ColorSet::new_dark_set(&scheme.secondary_palette),
 | 
				
			||||||
 | 
					            M3ColorSet::new_light_set(&scheme.secondary_palette)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_tertiary_color_set(scheme: &DynamicScheme) -> M3ColorSet {
 | 
				
			||||||
 | 
					    M3ColorSet {
 | 
				
			||||||
 | 
					        root: map_lch_to_srgb_hex(&TERTIARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root: map_lch_to_srgb_hex(&ON_TERTIARY.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container: map_lch_to_srgb_hex(&TERTIARY_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_container: map_lch_to_srgb_hex(&ON_TERTIARY_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed: map_lch_to_srgb_hex(&TERTIARY_FIXED.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_fixed: map_lch_to_srgb_hex(&ON_TERTIARY_FIXED.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_variant: map_lch_to_srgb_hex(&ON_TERTIARY_FIXED_VARIANT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_dim: map_lch_to_srgb_hex(&TERTIARY_FIXED_DIM.get_lch(scheme)),
 | 
				
			||||||
 | 
					        ..cond!(
 | 
				
			||||||
 | 
					            scheme.is_dark,
 | 
				
			||||||
 | 
					            M3ColorSet::new_dark_set(&scheme.tertiary_palette),
 | 
				
			||||||
 | 
					            M3ColorSet::new_light_set(&scheme.tertiary_palette)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_surface_color_set(scheme: &DynamicScheme) -> M3SurfaceSet {
 | 
				
			||||||
 | 
					    M3SurfaceSet {
 | 
				
			||||||
 | 
					        root: map_lch_to_srgb_hex(&SURFACE.get_lch(scheme)),
 | 
				
			||||||
 | 
					        dim: map_lch_to_srgb_hex(&SURFACE_DIM.get_lch(scheme)),
 | 
				
			||||||
 | 
					        bright: map_lch_to_srgb_hex(&SURFACE_BRIGHT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        variant: map_lch_to_srgb_hex(&SURFACE_VARIANT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container: map_lch_to_srgb_hex(&SURFACE_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container_lowest: map_lch_to_srgb_hex(&SURFACE_CONTAINER_LOWEST.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container_low: map_lch_to_srgb_hex(&SURFACE_CONTAINER_LOW.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container_high: map_lch_to_srgb_hex(&SURFACE_CONTAINER_HIGH.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container_highest: map_lch_to_srgb_hex(&SURFACE_CONTAINER_HIGHEST.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root: map_lch_to_srgb_hex(&ON_SURFACE.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root_variant: map_lch_to_srgb_hex(&ON_SURFACE_VARIANT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        inverse: map_lch_to_srgb_hex(&INVERSE_SURFACE.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_inverse: map_lch_to_srgb_hex(&INVERSE_ON_SURFACE.get_lch(scheme)),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_error_color_set(scheme: &DynamicScheme) -> M3ColorSet {
 | 
				
			||||||
 | 
					    M3ColorSet {
 | 
				
			||||||
 | 
					        root: map_lch_to_srgb_hex(&ERROR.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root: map_lch_to_srgb_hex(&ON_ERROR.get_lch(scheme)),
 | 
				
			||||||
 | 
					        container: map_lch_to_srgb_hex(&ERROR_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_container: map_lch_to_srgb_hex(&ON_ERROR_CONTAINER.get_lch(scheme)),
 | 
				
			||||||
 | 
					        ..cond!(
 | 
				
			||||||
 | 
					            scheme.is_dark,
 | 
				
			||||||
 | 
					            M3ColorSet::new_dark_set(&scheme.error_palette),
 | 
				
			||||||
 | 
					            M3ColorSet::new_light_set(&scheme.error_palette)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_custom_color_set(scheme: &DynamicScheme, name: String) -> M3ColorSet {
 | 
				
			||||||
 | 
					    M3ColorSet {
 | 
				
			||||||
 | 
					        root: map_lch_to_srgb_hex(&custom(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_root: map_lch_to_srgb_hex(&on_custom(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        container: map_lch_to_srgb_hex(&custom_container(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_container: map_lch_to_srgb_hex(&on_custom_container(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed: map_lch_to_srgb_hex(&custom_fixed(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        on_fixed: map_lch_to_srgb_hex(&on_custom_fixed(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_variant: map_lch_to_srgb_hex(&on_custom_fixed_variant(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        fixed_dim: map_lch_to_srgb_hex(&custom_fixed_dim(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					        inverse: map_lch_to_srgb_hex(&inverse_custom(name.clone()).get_lch(scheme)),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_baseline(scheme: &DynamicScheme) -> M3BaselineColors {
 | 
				
			||||||
 | 
					    M3BaselineColors::full_custom(
 | 
				
			||||||
 | 
					        build_primary_color_set(scheme),
 | 
				
			||||||
 | 
					        build_secondary_color_set(scheme),
 | 
				
			||||||
 | 
					        build_tertiary_color_set(scheme),
 | 
				
			||||||
 | 
					        build_error_color_set(scheme),
 | 
				
			||||||
 | 
					        build_surface_color_set(scheme),
 | 
				
			||||||
 | 
					        map_lch_to_srgb_hex(&OUTLINE.get_lch(scheme)),
 | 
				
			||||||
 | 
					        map_lch_to_srgb_hex(&OUTLINE_VARIANT.get_lch(scheme)),
 | 
				
			||||||
 | 
					        map_lch_to_srgb_hex(&SCRIM.get_lch(scheme)),
 | 
				
			||||||
 | 
					        map_lch_to_srgb_hex(&SHADOW.get_lch(scheme)),
 | 
				
			||||||
 | 
					        scheme
 | 
				
			||||||
 | 
					            .custom_palettes
 | 
				
			||||||
 | 
					            .keys()
 | 
				
			||||||
 | 
					            .map(|name| (name.clone(), build_custom_color_set(scheme, name.clone())))
 | 
				
			||||||
 | 
					            .collect(),
 | 
				
			||||||
 | 
					        scheme.is_dark,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_swatches(scheme: &DynamicScheme) -> HashMap<String, M3PaletteSwatch> {
 | 
				
			||||||
 | 
					    let mut swatches = HashMap::new();
 | 
				
			||||||
 | 
					    swatches.insert(
 | 
				
			||||||
 | 
					        "primary".to_string(),
 | 
				
			||||||
 | 
					        M3PaletteSwatch::new(&scheme.primary_palette),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    swatches.insert(
 | 
				
			||||||
 | 
					        "secondary".to_string(),
 | 
				
			||||||
 | 
					        M3PaletteSwatch::new(&scheme.secondary_palette),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    swatches.insert(
 | 
				
			||||||
 | 
					        "tertiary".to_string(),
 | 
				
			||||||
 | 
					        M3PaletteSwatch::new(&scheme.tertiary_palette),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    swatches.insert(
 | 
				
			||||||
 | 
					        "error".to_string(),
 | 
				
			||||||
 | 
					        M3PaletteSwatch::new(&scheme.error_palette),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    swatches.insert(
 | 
				
			||||||
 | 
					        "neutral".to_string(),
 | 
				
			||||||
 | 
					        M3PaletteSwatch::new(&scheme.neutral_palette),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    swatches.insert(
 | 
				
			||||||
 | 
					        "neutral_variant".to_string(),
 | 
				
			||||||
 | 
					        M3PaletteSwatch::new(&scheme.neutral_variant_palette),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (name, palette) in &scheme.custom_palettes {
 | 
				
			||||||
 | 
					        swatches.insert(name.clone(), M3PaletteSwatch::new(palette));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    swatches
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn build_dynamic_scheme(
 | 
				
			||||||
 | 
					    source_color: &str,
 | 
				
			||||||
 | 
					    error_color: Option<String>,
 | 
				
			||||||
 | 
					    custom_colors: HashMap<String, String>,
 | 
				
			||||||
 | 
					    variant: constants::Variant,
 | 
				
			||||||
 | 
					    contrast_level: f32,
 | 
				
			||||||
 | 
					    is_dark: bool,
 | 
				
			||||||
 | 
					    harmonize_customs: bool,
 | 
				
			||||||
 | 
					) -> Result<DynamicScheme, errors::ColorError> {
 | 
				
			||||||
 | 
					    let source_color = Srgb::from_str(source_color)
 | 
				
			||||||
 | 
					        .map_err(|_| errors::ColorError::UnrecogniazedRGB(source_color.to_string()))?
 | 
				
			||||||
 | 
					        .into_format::<f32>()
 | 
				
			||||||
 | 
					        .into_color();
 | 
				
			||||||
 | 
					    let error_color = error_color
 | 
				
			||||||
 | 
					        .map(|color| Srgb::from_str(&color))
 | 
				
			||||||
 | 
					        .transpose()
 | 
				
			||||||
 | 
					        .map_err(|_| errors::ColorError::UnrecogniazedRGB("error color".to_string()))?
 | 
				
			||||||
 | 
					        .map(|color| color.into_format::<f32>().into_color());
 | 
				
			||||||
 | 
					    let custom_colors = custom_colors
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .map(|(name, color)| {
 | 
				
			||||||
 | 
					            let color = Srgb::from_str(&color)
 | 
				
			||||||
 | 
					                .map_err(|_| errors::ColorError::UnrecogniazedRGB(color.clone()))?
 | 
				
			||||||
 | 
					                .into_format::<f32>()
 | 
				
			||||||
 | 
					                .into_color();
 | 
				
			||||||
 | 
					            Ok((name, color))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .collect::<Result<HashMap<String, Lch>, errors::ColorError>>()?;
 | 
				
			||||||
 | 
					    Ok(DynamicScheme::new(
 | 
				
			||||||
 | 
					        source_color,
 | 
				
			||||||
 | 
					        error_color,
 | 
				
			||||||
 | 
					        custom_colors,
 | 
				
			||||||
 | 
					        variant,
 | 
				
			||||||
 | 
					        contrast_level,
 | 
				
			||||||
 | 
					        is_dark,
 | 
				
			||||||
 | 
					        harmonize_customs,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,6 +2,7 @@ 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};
 | 
				
			||||||
@@ -10,11 +11,13 @@ 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;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -34,6 +37,7 @@ 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(),
 | 
				
			||||||
    ))
 | 
					    ))
 | 
				
			||||||
@@ -56,6 +60,7 @@ 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(),
 | 
				
			||||||
    ))
 | 
					    ))
 | 
				
			||||||
@@ -86,6 +91,7 @@ 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(),
 | 
				
			||||||
    ))
 | 
					    ))
 | 
				
			||||||
@@ -122,6 +128,7 @@ 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(),
 | 
				
			||||||
    ))
 | 
					    ))
 | 
				
			||||||
@@ -137,6 +144,55 @@ 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,3 +1,4 @@
 | 
				
			|||||||
 | 
					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,
 | 
				
			||||||
@@ -158,6 +159,37 @@ 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,3 +1,4 @@
 | 
				
			|||||||
 | 
					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};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -179,6 +180,47 @@ 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,6 +1,7 @@
 | 
				
			|||||||
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;
 | 
				
			||||||
@@ -130,6 +131,32 @@ 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,3 +1,5 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
@@ -84,23 +86,56 @@ 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));
 | 
					            variables.extend(swatch.to_css_variables("light", &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (name, swatch) in &self.dark {
 | 
					        for (name, swatch) in &self.dark {
 | 
				
			||||||
            variables.extend(swatch.to_css_variables("dark", name));
 | 
					            variables.extend(swatch.to_css_variables("dark", &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        variables.join("\n")
 | 
					        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));
 | 
					            variables.extend(swatch.to_scss_variables("light", &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        for (name, swatch) in &self.dark {
 | 
					        for (name, swatch) in &self.dark {
 | 
				
			||||||
            variables.extend(swatch.to_scss_variables("dark", name));
 | 
					            variables.extend(swatch.to_scss_variables("dark", &name.to_lowercase()));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        variables.join("\n")
 | 
					        variables.join("\n")
 | 
				
			||||||
@@ -114,7 +149,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_javascript_fields("light", &name.to_lowercase())
 | 
				
			||||||
                    .iter()
 | 
					                    .iter()
 | 
				
			||||||
                    .map(|s| format!("    {}", s)),
 | 
					                    .map(|s| format!("    {}", s)),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
@@ -125,7 +160,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_javascript_fields("dark", &name.to_lowercase())
 | 
				
			||||||
                    .iter()
 | 
					                    .iter()
 | 
				
			||||||
                    .map(|s| format!("    {}", s)),
 | 
					                    .map(|s| format!("    {}", s)),
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
@@ -94,6 +95,17 @@ 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,3 +106,33 @@ 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="/vite.svg" />
 | 
					  <link rel="icon" type="image/svg+xml" href="/logo.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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								logo.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<svg width="200px" height="200px" xmlns="http://www.w3.org/2000/svg">
 | 
				
			||||||
 | 
					  <defs/>
 | 
				
			||||||
 | 
					  <circle cx="100" cy="100" r="80" fill="none" stroke-width="20" style="stroke: rgb(235, 97, 255);"/>
 | 
				
			||||||
 | 
					  <circle cx="132" cy="132" r="16" fill="rgb(235, 97, 255)"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 289 B  | 
@@ -42,7 +42,6 @@ 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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,5 +17,9 @@
 | 
				
			|||||||
      background-color: #a78fff;
 | 
					      background-color: #a78fff;
 | 
				
			||||||
      color: var(--color-qihei);
 | 
					      color: var(--color-qihei);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    &.m3d {
 | 
				
			||||||
 | 
					      background-color: #ffde3f;
 | 
				
			||||||
 | 
					      color: var(--color-qihei);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,8 @@ 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,9 +31,7 @@ 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) && (
 | 
					      {!isNil(name) && <input type="hidden" name={name} value={isChecked ? 'true' : 'false'} />}
 | 
				
			||||||
        <input type="hidden" name={name} value={isChecked ? 'checked' : undefined} />
 | 
					 | 
				
			||||||
      )}
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,5 +18,9 @@
 | 
				
			|||||||
    &:hover {
 | 
					    &:hover {
 | 
				
			||||||
      color: var(--color-primary-hover);
 | 
					      color: var(--color-primary-hover);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    &.disabled {
 | 
				
			||||||
 | 
					      color: var(--color-primary-disabled);
 | 
				
			||||||
 | 
					      cursor: not-allowed;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,20 +3,30 @@ 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 TabProps = {
 | 
					type TabOption = {
 | 
				
			||||||
  tabs: { title: string; id: unknown }[];
 | 
					  title: string;
 | 
				
			||||||
  activeTab?: unknown;
 | 
					  id: string;
 | 
				
			||||||
  onActive?: (id: unknown) => void;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Tab({ tabs = [], activeTab, onActive }: TabProps) {
 | 
					type TabProps = {
 | 
				
			||||||
 | 
					  tabs: TabOption[];
 | 
				
			||||||
 | 
					  activeTab?: unknown;
 | 
				
			||||||
 | 
					  onActive?: (id: TabOption['id']) => void;
 | 
				
			||||||
 | 
					  disabled?: Record<TabOption['id'], boolean>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Tab({ tabs = [], activeTab, onActive, disabled }: TabProps) {
 | 
				
			||||||
  const [active, setActive] = useState(() =>
 | 
					  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((index: number) => {
 | 
					  const handleActivate = useCallback(
 | 
				
			||||||
 | 
					    (index: number) => {
 | 
				
			||||||
 | 
					      if (disabled?.[tabs[index].id] ?? false) return;
 | 
				
			||||||
      setActive(index);
 | 
					      setActive(index);
 | 
				
			||||||
      onActive?.(tabs[index].id);
 | 
					      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));
 | 
				
			||||||
@@ -30,7 +40,11 @@ export function Tab({ tabs = [], activeTab, onActive }: TabProps) {
 | 
				
			|||||||
      {tabs.map((tab, index) => (
 | 
					      {tabs.map((tab, index) => (
 | 
				
			||||||
        <div
 | 
					        <div
 | 
				
			||||||
          key={`tab_${index}_${tab.id}`}
 | 
					          key={`tab_${index}_${tab.id}`}
 | 
				
			||||||
          className={cx(styles.tab, isEqual(index, active) && styles.actived)}
 | 
					          className={cx(
 | 
				
			||||||
 | 
					            styles.tab,
 | 
				
			||||||
 | 
					            isEqual(index, active) && styles.actived,
 | 
				
			||||||
 | 
					            (disabled?.[tab.id] ?? false) && styles.disabled,
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
          onClick={() => handleActivate(index)}>
 | 
					          onClick={() => handleActivate(index)}>
 | 
				
			||||||
          {tab.title}
 | 
					          {tab.title}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,6 +32,7 @@ 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,6 +55,25 @@ 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,6 +1,9 @@
 | 
				
			|||||||
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 { MaterialDesign3SchemeStorage } from './material-3-scheme';
 | 
					import {
 | 
				
			||||||
 | 
					  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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,7 +32,12 @@ export type ColorDescription = {
 | 
				
			|||||||
  oklch: [number, number, number];
 | 
					  oklch: [number, number, number];
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type SchemeType = 'q_scheme' | 'swatch_scheme' | 'material_2' | 'material_3';
 | 
					export type SchemeType =
 | 
				
			||||||
 | 
					  | '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;
 | 
				
			||||||
@@ -40,6 +48,7 @@ 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(
 | 
				
			||||||
@@ -72,4 +81,5 @@ export type SchemeStorage =
 | 
				
			|||||||
  | QSchemeStorage
 | 
					  | QSchemeStorage
 | 
				
			||||||
  | SwatchSchemeStorage
 | 
					  | SwatchSchemeStorage
 | 
				
			||||||
  | MaterialDesign2SchemeStorage
 | 
					  | MaterialDesign2SchemeStorage
 | 
				
			||||||
  | MaterialDesign3SchemeStorage;
 | 
					  | MaterialDesign3SchemeStorage
 | 
				
			||||||
 | 
					  | MaterialDesign3DynamicSchemeStorage;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ 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' },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
@@ -22,6 +23,8 @@ 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,6 +3,7 @@ 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';
 | 
				
			||||||
@@ -24,7 +25,15 @@ export function M2Scheme({ scheme }: M2SchemeProps) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
 | 
					      <Tab
 | 
				
			||||||
 | 
					        tabs={tabOptions}
 | 
				
			||||||
 | 
					        activeTab={activeTab}
 | 
				
			||||||
 | 
					        onActive={(v) => setActiveTab(v as string)}
 | 
				
			||||||
 | 
					        disabled={{
 | 
				
			||||||
 | 
					          overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
 | 
				
			||||||
 | 
					          export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
      {isEqual(activeTab, 'overview') && <M2SchemePreview scheme={scheme} />}
 | 
					      {isEqual(activeTab, 'overview') && <M2SchemePreview scheme={scheme} />}
 | 
				
			||||||
      {isEqual(activeTab, 'builder') && (
 | 
					      {isEqual(activeTab, 'builder') && (
 | 
				
			||||||
        <M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />
 | 
					        <M2SchemeBuilder scheme={scheme} onBuildComplete={() => setActiveTab('overview')} />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										44
									
								
								src/page-components/scheme/M3DynamicScheme.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/page-components/scheme/M3DynamicScheme.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					import { isEqual, isNil } from 'lodash-es';
 | 
				
			||||||
 | 
					import { useState } from 'react';
 | 
				
			||||||
 | 
					import { Tab } from '../../components/Tab';
 | 
				
			||||||
 | 
					import { MaterialDesign3DynamicSchemeStorage } from '../../material-3-scheme';
 | 
				
			||||||
 | 
					import { SchemeContent } from '../../models';
 | 
				
			||||||
 | 
					import { isNilOrEmpty } from '../../utls';
 | 
				
			||||||
 | 
					import { SchemeExport } from './Export';
 | 
				
			||||||
 | 
					import { M3DynamicSchemeBuilder } from './m3-dynamic-scheme/Builder';
 | 
				
			||||||
 | 
					import { M3SchemePreview } from './m3-scheme/Preview';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tabOptions = [
 | 
				
			||||||
 | 
					  { title: 'Overview', id: 'overview' },
 | 
				
			||||||
 | 
					  { title: 'Builder', id: 'builder' },
 | 
				
			||||||
 | 
					  { title: 'Exports', id: 'export' },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type M3SchemeProps = {
 | 
				
			||||||
 | 
					  scheme: SchemeContent<MaterialDesign3DynamicSchemeStorage>;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function M3DynamicScheme({ scheme }: M3SchemeProps) {
 | 
				
			||||||
 | 
					  const [activeTab, setActiveTab] = useState<(typeof tabOptions)[number]['id']>(() =>
 | 
				
			||||||
 | 
					    isNil(scheme.schemeStorage.scheme) ? 'builder' : 'overview',
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Tab
 | 
				
			||||||
 | 
					        tabs={tabOptions}
 | 
				
			||||||
 | 
					        activeTab={activeTab}
 | 
				
			||||||
 | 
					        onActive={(v) => setActiveTab(v as string)}
 | 
				
			||||||
 | 
					        disabled={{
 | 
				
			||||||
 | 
					          overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
 | 
				
			||||||
 | 
					          export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      {isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme.schemeStorage.scheme} />}
 | 
				
			||||||
 | 
					      {isEqual(activeTab, 'builder') && (
 | 
				
			||||||
 | 
					        <M3DynamicSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {isEqual(activeTab, 'export') && <SchemeExport scheme={scheme} />}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,6 +3,7 @@ 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';
 | 
				
			||||||
@@ -24,8 +25,16 @@ export function M3Scheme({ scheme }: M3SchemeProps) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
 | 
					      <Tab
 | 
				
			||||||
      {isEqual(activeTab, 'overview') && <M3SchemePreview scheme={scheme} />}
 | 
					        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') && (
 | 
					      {isEqual(activeTab, 'builder') && (
 | 
				
			||||||
        <M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
 | 
					        <M3SchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ 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';
 | 
				
			||||||
@@ -24,7 +25,15 @@ export function QScheme({ scheme }: QSchemeProps) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
 | 
					      <Tab
 | 
				
			||||||
 | 
					        tabs={tabOptions}
 | 
				
			||||||
 | 
					        activeTab={activeTab}
 | 
				
			||||||
 | 
					        onActive={(v) => setActiveTab(v as string)}
 | 
				
			||||||
 | 
					        disabled={{
 | 
				
			||||||
 | 
					          overview: isNilOrEmpty(scheme.schemeStorage?.scheme),
 | 
				
			||||||
 | 
					          export: isNilOrEmpty(scheme.schemeStorage?.cssVariables),
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
      {isEqual(activeTab, 'overview') && <QSchemePreview scheme={scheme} />}
 | 
					      {isEqual(activeTab, 'overview') && <QSchemePreview scheme={scheme} />}
 | 
				
			||||||
      {isEqual(activeTab, 'builder') && (
 | 
					      {isEqual(activeTab, 'builder') && (
 | 
				
			||||||
        <QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
 | 
					        <QSchemeBuilder scheme={scheme} onBuildCompleted={() => setActiveTab('overview')} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ 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';
 | 
				
			||||||
@@ -24,7 +25,15 @@ export function SwatchScheme({ scheme }: SwatchSchemeProps) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <Tab tabs={tabOptions} activeTab={activeTab} onActive={(v) => setActiveTab(v as string)} />
 | 
					      <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') && <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,6 +23,12 @@
 | 
				
			|||||||
      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,8 +2,12 @@ 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 { MaterialDesign2SchemeStorage } from '../../../material-2-scheme';
 | 
					import {
 | 
				
			||||||
 | 
					  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';
 | 
				
			||||||
@@ -16,6 +20,7 @@ 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(() => {
 | 
				
			||||||
@@ -35,25 +40,10 @@ 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 [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
 | 
					 | 
				
			||||||
    (_state, formData) => {
 | 
					 | 
				
			||||||
      const errMsg = new Map<string, string>();
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
    const primaryColor = formData.get('primary') as string;
 | 
					    const primaryColor = formData.get('primary') as string;
 | 
				
			||||||
        if (isNil(primaryColor) || isEmpty(primaryColor)) {
 | 
					 | 
				
			||||||
          errMsg.set('primary', 'Primary color is required');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    const secondaryColor = formData.get('secondary') as string;
 | 
					    const secondaryColor = formData.get('secondary') as string;
 | 
				
			||||||
        if (isNil(secondaryColor) || isEmpty(secondaryColor)) {
 | 
					 | 
				
			||||||
          errMsg.set('secondary', 'Secondary color is required');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    const errorColor = formData.get('error') as string;
 | 
					    const errorColor = formData.get('error') as string;
 | 
				
			||||||
        if (isNil(errorColor) || isEmpty(errorColor)) {
 | 
					 | 
				
			||||||
          errMsg.set('error', 'Error color is required');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!isEmpty(errMsg)) return errMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const customColors: Record<string, string> = {};
 | 
					    const customColors: Record<string, string> = {};
 | 
				
			||||||
    for (const key of colorKeys) {
 | 
					    for (const key of colorKeys) {
 | 
				
			||||||
      const name = formData.get(`name_${key}`) as string;
 | 
					      const name = formData.get(`name_${key}`) as string;
 | 
				
			||||||
@@ -61,26 +51,64 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
 | 
				
			|||||||
      if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
 | 
					      if (isNil(name) || isEmpty(name) || isNil(color) || isEmpty(color)) continue;
 | 
				
			||||||
      customColors[name] = color;
 | 
					      customColors[name] = color;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        const generatedScheme = colorFn?.generate_material_design_2_scheme(
 | 
					
 | 
				
			||||||
          primaryColor,
 | 
					    return {
 | 
				
			||||||
          secondaryColor,
 | 
					      primary: isNil(primaryColor) || isEmpty(primaryColor) ? null : primaryColor,
 | 
				
			||||||
          errorColor,
 | 
					      secondary: isNil(secondaryColor) || isEmpty(secondaryColor) ? null : secondaryColor,
 | 
				
			||||||
          customColors,
 | 
					      error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        updateScheme((prev) => {
 | 
					 | 
				
			||||||
          prev.schemeStorage.source = {
 | 
					 | 
				
			||||||
            primary: primaryColor,
 | 
					 | 
				
			||||||
            secondary: secondaryColor,
 | 
					 | 
				
			||||||
            error: errorColor,
 | 
					 | 
				
			||||||
      custom_colors: customColors,
 | 
					      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>(
 | 
				
			||||||
 | 
					    (_state, formData) => {
 | 
				
			||||||
 | 
					      const errMsg = new Map<string, string>();
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const collected = colectSchemeSource(formData);
 | 
				
			||||||
 | 
					        if (isNil(collected.primary) || isEmpty(collected.primary)) {
 | 
				
			||||||
 | 
					          errMsg.set('primary', 'Primary color is required');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (isNil(collected.secondary) || isEmpty(collected.secondary)) {
 | 
				
			||||||
 | 
					          errMsg.set('secondary', 'Secondary color is required');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (isNil(collected.error) || isEmpty(collected.error)) {
 | 
				
			||||||
 | 
					          errMsg.set('error', 'Error color is required');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!isEmpty(errMsg)) return errMsg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const generatedScheme = colorFn?.generate_material_design_2_scheme(
 | 
				
			||||||
 | 
					          collected.primary,
 | 
				
			||||||
 | 
					          collected.secondary,
 | 
				
			||||||
 | 
					          collected.error,
 | 
				
			||||||
 | 
					          collected.custom_colors,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        updateScheme((prev) => {
 | 
				
			||||||
 | 
					          prev.schemeStorage.source = collected;
 | 
				
			||||||
          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.scssVariables = generatedScheme[2];
 | 
					          prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
 | 
				
			||||||
          prev.schemeStorage.jsVariables = generatedScheme[3];
 | 
					          prev.schemeStorage.scssVariables = generatedScheme[3];
 | 
				
			||||||
 | 
					          prev.schemeStorage.jsVariables = generatedScheme[4];
 | 
				
			||||||
          return prev;
 | 
					          return prev;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -167,10 +195,13 @@ export function M2SchemeBuilder({ scheme, onBuildComplete }: M2SchemeBuilderProp
 | 
				
			|||||||
              onDelete={(index) => setDeleted((prev) => [...prev, index])}
 | 
					              onDelete={(index) => setDeleted((prev) => [...prev, index])}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          ))}
 | 
					          ))}
 | 
				
			||||||
        <div style={{ gridColumn: '2 / span 2' }}>
 | 
					        <div className={styles.button_row} 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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					@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);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										245
									
								
								src/page-components/scheme/m3-dynamic-scheme/Builder.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								src/page-components/scheme/m3-dynamic-scheme/Builder.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,245 @@
 | 
				
			|||||||
 | 
					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,6 +23,12 @@
 | 
				
			|||||||
      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,8 +2,13 @@ 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 { MaterialDesign3Scheme, MaterialDesign3SchemeStorage } from '../../../material-3-scheme';
 | 
					import {
 | 
				
			||||||
 | 
					  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';
 | 
				
			||||||
@@ -16,6 +21,7 @@ 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(() => {
 | 
				
			||||||
@@ -36,21 +42,9 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
 | 
				
			|||||||
    [originalColors, newColors, deleted],
 | 
					    [originalColors, newColors, deleted],
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
 | 
					  const collectSchemeSource = (formData: FormData): MaterialDesign3SchemeSource => {
 | 
				
			||||||
    (_state, formData) => {
 | 
					 | 
				
			||||||
      const errMsg = new Map<string, string>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
    const sourceColor = formData.get('source') as string;
 | 
					    const sourceColor = formData.get('source') as string;
 | 
				
			||||||
        if (isNil(sourceColor) || isEmpty(sourceColor)) {
 | 
					 | 
				
			||||||
          errMsg.set('source', 'Source color is required');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    const errorColor = formData.get('error') as string;
 | 
					    const errorColor = formData.get('error') as string;
 | 
				
			||||||
        if (isNil(errorColor) || isEmpty(errorColor)) {
 | 
					 | 
				
			||||||
          errMsg.set('error', 'Error color is required');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (!isEmpty(errMsg)) return errMsg;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const customColors: Record<string, string> = {};
 | 
					    const customColors: Record<string, string> = {};
 | 
				
			||||||
    for (const key of colorKeys) {
 | 
					    for (const key of colorKeys) {
 | 
				
			||||||
      const name = formData.get(`name_${key}`) as string;
 | 
					      const name = formData.get(`name_${key}`) as string;
 | 
				
			||||||
@@ -59,17 +53,51 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
 | 
				
			|||||||
      customColors[name] = color;
 | 
					      customColors[name] = color;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const generatedScheme = colorFn?.generate_material_design_3_scheme(
 | 
					    return {
 | 
				
			||||||
          sourceColor,
 | 
					      source: isNil(sourceColor) || isEmpty(sourceColor) ? null : sourceColor,
 | 
				
			||||||
          errorColor,
 | 
					      error: isNil(errorColor) || isEmpty(errorColor) ? null : errorColor,
 | 
				
			||||||
          customColors,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        updateScheme((prev) => {
 | 
					 | 
				
			||||||
          prev.schemeStorage.source = {
 | 
					 | 
				
			||||||
            source: sourceColor as string,
 | 
					 | 
				
			||||||
            error: errorColor as string,
 | 
					 | 
				
			||||||
      custom_colors: customColors,
 | 
					      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>(
 | 
				
			||||||
 | 
					    (_state, formData) => {
 | 
				
			||||||
 | 
					      const errMsg = new Map<string, string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const collectedSource = collectSchemeSource(formData);
 | 
				
			||||||
 | 
					        if (isNil(collectedSource.source) || isEmpty(collectedSource.source)) {
 | 
				
			||||||
 | 
					          errMsg.set('source', 'Source color is required');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (isNil(collectedSource.error) || isEmpty(collectedSource.error)) {
 | 
				
			||||||
 | 
					          errMsg.set('error', 'Error color is required');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (!isEmpty(errMsg)) return errMsg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const generatedScheme = colorFn?.generate_material_design_3_scheme(
 | 
				
			||||||
 | 
					          collectedSource.source,
 | 
				
			||||||
 | 
					          collectedSource.error,
 | 
				
			||||||
 | 
					          collectedSource.custom_colors,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        updateScheme((prev) => {
 | 
				
			||||||
 | 
					          prev.schemeStorage.source = collectedSource;
 | 
				
			||||||
          prev.schemeStorage.scheme = {
 | 
					          prev.schemeStorage.scheme = {
 | 
				
			||||||
            white: generatedScheme[0].white,
 | 
					            white: generatedScheme[0].white,
 | 
				
			||||||
            black: generatedScheme[0].black,
 | 
					            black: generatedScheme[0].black,
 | 
				
			||||||
@@ -83,8 +111,9 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
          } as MaterialDesign3Scheme;
 | 
					          } as MaterialDesign3Scheme;
 | 
				
			||||||
          prev.schemeStorage.cssVariables = generatedScheme[1];
 | 
					          prev.schemeStorage.cssVariables = generatedScheme[1];
 | 
				
			||||||
          prev.schemeStorage.scssVariables = generatedScheme[2];
 | 
					          prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
 | 
				
			||||||
          prev.schemeStorage.jsVariables = generatedScheme[3];
 | 
					          prev.schemeStorage.scssVariables = generatedScheme[3];
 | 
				
			||||||
 | 
					          prev.schemeStorage.jsVariables = generatedScheme[4];
 | 
				
			||||||
          return prev;
 | 
					          return prev;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -154,10 +183,13 @@ export function M3SchemeBuilder({ scheme, onBuildCompleted }: M3SchemeBuilderPro
 | 
				
			|||||||
              onDelete={(index) => setDeleted((prev) => [...prev, index])}
 | 
					              onDelete={(index) => setDeleted((prev) => [...prev, index])}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
          ))}
 | 
					          ))}
 | 
				
			||||||
        <div style={{ gridColumn: '2 / span 2' }}>
 | 
					        <div className={styles.button_row} 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,11 +1,5 @@
 | 
				
			|||||||
import { ScrollArea } from '../../../components/ScrollArea';
 | 
					import { ScrollArea } from '../../../components/ScrollArea';
 | 
				
			||||||
import {
 | 
					import { Baseline, ColorSet, MaterialDesign3Scheme, Surface } from '../../../material-3-scheme';
 | 
				
			||||||
  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 = {
 | 
				
			||||||
@@ -257,15 +251,15 @@ function PreviewBlock({ title, baseline }: PreviewBlockProps) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type M3SchemePreviewProps = {
 | 
					type M3SchemePreviewProps = {
 | 
				
			||||||
  scheme: SchemeContent<MaterialDesign3SchemeStorage>;
 | 
					  scheme: MaterialDesign3Scheme;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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.schemeStorage.scheme!.light_baseline} />
 | 
					        <PreviewBlock title="Light Scheme" baseline={scheme.light_baseline} />
 | 
				
			||||||
        <PreviewBlock title="Dark Scheme" baseline={scheme.schemeStorage.scheme!.dark_baseline} />
 | 
					        <PreviewBlock title="Dark Scheme" baseline={scheme.dark_baseline} />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </ScrollArea>
 | 
					    </ScrollArea>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -163,8 +163,9 @@ export function QSchemeBuilder({ scheme, onBuildCompleted }: QSchemeBuilderProps
 | 
				
			|||||||
          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.scssVariables = generatedScheme[2];
 | 
					          prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
 | 
				
			||||||
          prev.schemeStorage.jsVariables = generatedScheme[3];
 | 
					          prev.schemeStorage.scssVariables = generatedScheme[3];
 | 
				
			||||||
 | 
					          prev.schemeStorage.jsVariables = generatedScheme[4];
 | 
				
			||||||
          return prev;
 | 
					          return prev;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        onBuildCompleted?.();
 | 
					        onBuildCompleted?.();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,12 @@
 | 
				
			|||||||
    .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,6 +2,7 @@ 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';
 | 
				
			||||||
@@ -10,6 +11,7 @@ import {
 | 
				
			|||||||
  QSwatchEntry,
 | 
					  QSwatchEntry,
 | 
				
			||||||
  QSwatchSchemeSetting,
 | 
					  QSwatchSchemeSetting,
 | 
				
			||||||
  SwatchScheme,
 | 
					  SwatchScheme,
 | 
				
			||||||
 | 
					  SwatchSchemeSource,
 | 
				
			||||||
  SwatchSchemeStorage,
 | 
					  SwatchSchemeStorage,
 | 
				
			||||||
} from '../../../swatch_scheme';
 | 
					} from '../../../swatch_scheme';
 | 
				
			||||||
import { mapToObject } from '../../../utls';
 | 
					import { mapToObject } from '../../../utls';
 | 
				
			||||||
@@ -22,6 +24,7 @@ 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(() => {
 | 
				
			||||||
@@ -64,30 +67,10 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
  }, [scheme.schemeStorage.source]);
 | 
					  }, [scheme.schemeStorage.source]);
 | 
				
			||||||
 | 
					  const collectSchemeSource = (formData: FormData): SwatchSchemeSource => {
 | 
				
			||||||
  const [errMsg, handleSubmitAction] = useActionState<Map<string, string>, FormData>(
 | 
					 | 
				
			||||||
    (_state, formData) => {
 | 
					 | 
				
			||||||
      const errMsg = new Map<string, string>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
    const swatchAmount = Number(formData.get('amount'));
 | 
					    const swatchAmount = Number(formData.get('amount'));
 | 
				
			||||||
        if (isNaN(swatchAmount) || swatchAmount <= 0) {
 | 
					 | 
				
			||||||
          errMsg.set('amount', 'MUST be a positive number');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (swatchAmount > 30) {
 | 
					 | 
				
			||||||
          errMsg.set('amount', 'MUST be less than 30');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const minLightness = Number(formData.get('min_lightness'));
 | 
					    const minLightness = Number(formData.get('min_lightness'));
 | 
				
			||||||
        if (isNaN(minLightness) || minLightness < 0 || minLightness > 100) {
 | 
					 | 
				
			||||||
          errMsg.set('min', 'MUST be a number between 0 and 100');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const maxLightness = Number(formData.get('max_lightness'));
 | 
					    const maxLightness = Number(formData.get('max_lightness'));
 | 
				
			||||||
        if (isNaN(maxLightness) || maxLightness < 0 || maxLightness > 100) {
 | 
					 | 
				
			||||||
          errMsg.set('max', 'MUST be a number between 0 and 100');
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const includePrimary = isEqual(formData.get('include_primary'), 'true');
 | 
					    const includePrimary = isEqual(formData.get('include_primary'), 'true');
 | 
				
			||||||
    const darkConvertChroma = Number(formData.get('dark_chroma')) / 100.0;
 | 
					    const darkConvertChroma = Number(formData.get('dark_chroma')) / 100.0;
 | 
				
			||||||
    const darkConvertLightness = Number(formData.get('dark_lightness')) / 100.0;
 | 
					    const darkConvertLightness = Number(formData.get('dark_lightness')) / 100.0;
 | 
				
			||||||
@@ -108,23 +91,76 @@ export function SwatchSchemeBuilder({ scheme, onBuildCompleted }: SwatchSchemeBu
 | 
				
			|||||||
      entries.push(new SwatchEntry(name, color));
 | 
					      entries.push(new SwatchEntry(name, color));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const dumpedEntries = entries.map((entry) => entry.toJsValue() as QSwatchEntry);
 | 
					    const dumpedEntries = entries.map((entry) => entry.toJsValue() as QSwatchEntry);
 | 
				
			||||||
        if (isEmpty(entries)) {
 | 
					
 | 
				
			||||||
 | 
					    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>(
 | 
				
			||||||
 | 
					    (_state, formData) => {
 | 
				
			||||||
 | 
					      const errMsg = new Map<string, string>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        const collected = collectSchemeSource(formData);
 | 
				
			||||||
 | 
					        if (isNaN(collected.setting.amount) || collected.setting.amount <= 0) {
 | 
				
			||||||
 | 
					          errMsg.set('amount', 'MUST be a positive number');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (collected.setting.amount > 30) {
 | 
				
			||||||
 | 
					          errMsg.set('amount', 'MUST be less than 30');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          isNaN(collected.setting.min_lightness) ||
 | 
				
			||||||
 | 
					          collected.setting.min_lightness < 0 ||
 | 
				
			||||||
 | 
					          collected.setting.min_lightness > 1.0
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          errMsg.set('min', 'MUST be a number between 0 and 100');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (
 | 
				
			||||||
 | 
					          isNaN(collected.setting.max_lightness) ||
 | 
				
			||||||
 | 
					          collected.setting.max_lightness < 0 ||
 | 
				
			||||||
 | 
					          collected.setting.max_lightness > 1.0
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          errMsg.set('max', 'MUST be a number between 0 and 100');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isEmpty(collected.colors)) {
 | 
				
			||||||
          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(entries, swatchSetting);
 | 
					        const generatedScheme = colorFn?.generate_swatch_scheme(
 | 
				
			||||||
        console.debug('[generated scheme]', generatedScheme);
 | 
					          collected.colors,
 | 
				
			||||||
 | 
					          collected.setting,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
        updateScheme((prev) => {
 | 
					        updateScheme((prev) => {
 | 
				
			||||||
          prev.schemeStorage.source = {
 | 
					          prev.schemeStorage.source = collected;
 | 
				
			||||||
            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.scssVariables = generatedScheme[2];
 | 
					          prev.schemeStorage.cssAutoSchemeVariables = generatedScheme[2];
 | 
				
			||||||
          prev.schemeStorage.jsVariables = generatedScheme[3];
 | 
					          prev.schemeStorage.scssVariables = generatedScheme[3];
 | 
				
			||||||
 | 
					          prev.schemeStorage.jsVariables = generatedScheme[4];
 | 
				
			||||||
          return prev;
 | 
					          return prev;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -236,10 +272,13 @@ 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 style={{ gridColumn: '2 / span 2' }}>
 | 
					        <div className={styles.button_row} 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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ 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';
 | 
				
			||||||
@@ -20,7 +21,11 @@ export function CardsDetail({ mainTag }: CardsDetailProps) {
 | 
				
			|||||||
      return [];
 | 
					      return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const embededCategories = colorFn.color_categories() as { label: string; value: string }[];
 | 
					      const embededCategories = colorFn.color_categories().map(mapToObject) as {
 | 
				
			||||||
 | 
					        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,10 +6,14 @@ 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 { MaterialDesign3SchemeStorage } from '../material-3-scheme';
 | 
					import {
 | 
				
			||||||
 | 
					  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';
 | 
				
			||||||
@@ -52,6 +56,10 @@ 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,6 +55,7 @@ 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;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ 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,6 +15,10 @@ 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,4 +10,7 @@ export default defineConfig({
 | 
				
			|||||||
      cssModules: true,
 | 
					      cssModules: true,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  optimizeDeps: {
 | 
				
			||||||
 | 
					    exclude: ['color-module'],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user