增加M3动态配色Scheme的生成功能。
This commit is contained in:
parent
31cedd2547
commit
715459eef0
|
@ -135,3 +135,14 @@ pub fn wacg_relative_contrast(fg_color: &str, bg_color: &str) -> Result<f32, err
|
|||
.into_format::<f32>();
|
||||
Ok(fg_srgb.relative_contrast(bg_srgb))
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! cond {
|
||||
($s: expr, $a: expr, $b: expr) => {
|
||||
if $s {
|
||||
$a
|
||||
} else {
|
||||
$b
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -80,6 +80,34 @@ impl M3BaselineColors {
|
|||
self.customs.insert(name, color_set_generator(c));
|
||||
}
|
||||
|
||||
pub fn full_custom(
|
||||
primary: M3ColorSet,
|
||||
secondary: M3ColorSet,
|
||||
tertiary: M3ColorSet,
|
||||
error: M3ColorSet,
|
||||
surface: M3SurfaceSet,
|
||||
outline: String,
|
||||
outline_variant: String,
|
||||
scrim: String,
|
||||
shadow: String,
|
||||
customs: HashMap<String, M3ColorSet>,
|
||||
dark_set: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
primary,
|
||||
secondary,
|
||||
tertiary,
|
||||
error,
|
||||
surface,
|
||||
outline,
|
||||
outline_variant,
|
||||
scrim,
|
||||
shadow,
|
||||
customs,
|
||||
dark_set,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_css_variables(&self) -> Vec<String> {
|
||||
let mut css_variables = Vec::new();
|
||||
let prefix = if self.dark_set { "dark" } else { "light" };
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use baseline::M3BaselineColors;
|
||||
pub use baseline::M3BaselineColors;
|
||||
pub use color_set::M3ColorSet;
|
||||
use palette::{IntoColor, Lch, Srgb};
|
||||
use serde::Serialize;
|
||||
use tonal_palette::TonalPalette;
|
||||
pub use surface::M3SurfaceSet;
|
||||
pub use tonal_palette::TonalPalette;
|
||||
|
||||
use crate::convert::map_lch_to_srgb_hex;
|
||||
use crate::errors;
|
||||
|
@ -64,6 +66,15 @@ impl MaterialDesign3Scheme {
|
|||
self.dark_baseline.add_custom_set(name.clone(), &palette);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn full_custom(light_baseline: M3BaselineColors, dark_baseline: M3BaselineColors) -> 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeExport for MaterialDesign3Scheme {
|
||||
|
|
|
@ -9,6 +9,7 @@ pub struct M3SurfaceSet {
|
|||
pub root: String,
|
||||
pub dim: String,
|
||||
pub bright: String,
|
||||
pub variant: String,
|
||||
pub container: String,
|
||||
pub container_lowest: String,
|
||||
pub container_low: String,
|
||||
|
@ -25,6 +26,7 @@ impl M3SurfaceSet {
|
|||
let root = neutral.tone(98.0);
|
||||
let dim = neutral.tone(87.0);
|
||||
let bright = neutral.tone(98.0);
|
||||
let variant = neutral_variant.tone(90.0);
|
||||
let container = neutral.tone(94.0);
|
||||
let container_lowest = neutral.tone(100.0);
|
||||
let container_low = neutral.tone(96.0);
|
||||
|
@ -39,6 +41,7 @@ impl M3SurfaceSet {
|
|||
root: map_lch_to_srgb_hex(&root),
|
||||
dim: map_lch_to_srgb_hex(&dim),
|
||||
bright: map_lch_to_srgb_hex(&bright),
|
||||
variant: map_lch_to_srgb_hex(&variant),
|
||||
container: map_lch_to_srgb_hex(&container),
|
||||
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_lch_to_srgb_hex(&container_low),
|
||||
|
@ -55,6 +58,7 @@ impl M3SurfaceSet {
|
|||
let root = neutral.tone(6.0);
|
||||
let dim = neutral.tone(6.0);
|
||||
let bright = neutral.tone(24.0);
|
||||
let variant = neutral_variant.tone(30.0);
|
||||
let container = neutral.tone(12.0);
|
||||
let container_lowest = neutral.tone(4.0);
|
||||
let container_low = neutral.tone(10.0);
|
||||
|
@ -69,6 +73,7 @@ impl M3SurfaceSet {
|
|||
root: map_lch_to_srgb_hex(&root),
|
||||
dim: map_lch_to_srgb_hex(&dim),
|
||||
bright: map_lch_to_srgb_hex(&bright),
|
||||
variant: map_lch_to_srgb_hex(&variant),
|
||||
container: map_lch_to_srgb_hex(&container),
|
||||
container_lowest: map_lch_to_srgb_hex(&container_lowest),
|
||||
container_low: map_lch_to_srgb_hex(&container_low),
|
||||
|
@ -90,6 +95,10 @@ impl M3SurfaceSet {
|
|||
"--color-{}-surface-bright: #{};",
|
||||
prefix, self.bright
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-variant: #{};",
|
||||
prefix, self.variant
|
||||
));
|
||||
css_variables.push(format!(
|
||||
"--color-{}-surface-container: #{};",
|
||||
prefix, self.container
|
||||
|
@ -136,6 +145,10 @@ impl M3SurfaceSet {
|
|||
"$color-{}-surface-bright: #{};",
|
||||
prefix, self.bright
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-variant: #{};",
|
||||
prefix, self.variant
|
||||
));
|
||||
scss_variables.push(format!(
|
||||
"$color-{}-surface-container: #{};",
|
||||
prefix, self.container
|
||||
|
@ -179,6 +192,7 @@ impl M3SurfaceSet {
|
|||
js_object_fields.push(format!("{}Surface: '#{}',", prefix, self.root));
|
||||
js_object_fields.push(format!("{}SurfaceDim: '#{}',", prefix, self.dim));
|
||||
js_object_fields.push(format!("{}SurfaceBright: '#{}',", prefix, self.bright));
|
||||
js_object_fields.push(format!("{}SurfaceVariant: '#{}',", prefix, self.variant));
|
||||
js_object_fields.push(format!(
|
||||
"{}SurfaceContainer: '#{}',",
|
||||
prefix, self.container
|
||||
|
|
363
color-module/src/schemes/material_design_3_dynamic/constants.rs
Normal file
363
color-module/src/schemes/material_design_3_dynamic/constants.rs
Normal file
|
@ -0,0 +1,363 @@
|
|||
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 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,239 @@
|
|||
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) -> 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: DynamicColor,
|
||||
pub role_b: 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 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.get_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.get_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,867 @@
|
|||
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 {
|
||||
($name: ident, $palette: expr, $tone: expr) => {
|
||||
pub fn $name() -> DynamicColor {
|
||||
DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
};
|
||||
($name: ident, $palette: expr, $tone: expr, $is_background: expr) => {
|
||||
pub fn $name() -> DynamicColor {
|
||||
DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
Some($is_background),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
};
|
||||
($name: ident, $palette: expr, $tone: expr, $background: expr, $contrast_curve: expr) => {
|
||||
pub fn $name() -> DynamicColor {
|
||||
DynamicColor::new(
|
||||
Some(stringify!($name)),
|
||||
Box::new($palette),
|
||||
Box::new($tone),
|
||||
None,
|
||||
Some(Box::new($background)),
|
||||
None,
|
||||
Some($contrast_curve),
|
||||
None,
|
||||
)
|
||||
}
|
||||
};
|
||||
($name: ident, $palette: expr, $tone: expr, $is_background: expr, $background: expr, $secondary_background: expr, $contrast_curve: expr, $tone_delta_pairs: expr) => {
|
||||
pub fn $name() -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
cond!(s.is_dark, surface_bright(), surface())
|
||||
}
|
||||
|
||||
dynamic_gen!(
|
||||
surface,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 6.0, 98.0),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
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,
|
||||
|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,
|
||||
|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,
|
||||
|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,
|
||||
|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,
|
||||
|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,
|
||||
|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,
|
||||
|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,
|
||||
|s: &DynamicScheme| s.neutral_variant_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 30.0, 90.0),
|
||||
true
|
||||
);
|
||||
dynamic_gen!(
|
||||
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,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 20.0, 95.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
inverse_on_surface,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 20.0, 95.0),
|
||||
|_| inverse_surface(),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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,
|
||||
|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,
|
||||
|s: &DynamicScheme| s.neutral_palette.clone(),
|
||||
|_| 0.0
|
||||
);
|
||||
dynamic_gen!(scrim, |s: &DynamicScheme| s.neutral_palette.clone(), |_| {
|
||||
0.0
|
||||
});
|
||||
dynamic_gen!(
|
||||
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: primary_container(),
|
||||
role_b: primary(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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)
|
||||
}
|
||||
},
|
||||
|_| primary(),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: primary_container(),
|
||||
role_b: primary(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_primary_container,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| {
|
||||
if is_fidelity(s) {
|
||||
return foreground_tone(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)
|
||||
}
|
||||
},
|
||||
|_| primary_container(),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
inverse_primary,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 40.0, 80.0),
|
||||
|_| inverse_surface(),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 7.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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,
|
||||
|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)
|
||||
}
|
||||
},
|
||||
|_| secondary(),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: secondary_container(),
|
||||
role_b: secondary(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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(secondary_container().get_tone(s), 4.5)
|
||||
},
|
||||
|_| secondary_container(),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: tertiary_container(),
|
||||
role_b: tertiary(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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)
|
||||
},
|
||||
|_| tertiary(),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: tertiary_container(),
|
||||
role_b: tertiary(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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(tertiary_container().get_tone(s), 4.5)
|
||||
},
|
||||
|_| tertiary_container(),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: error_container(),
|
||||
role_b: error(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_error,
|
||||
|s: &DynamicScheme| s.error_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(s.is_dark, 20.0, 100.0),
|
||||
|_| error(),
|
||||
ContrastCurve::new(4.5, 7.0, 11.0, 21.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: error_container(),
|
||||
role_b: error(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Nearer,
|
||||
togather: false,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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)
|
||||
}
|
||||
},
|
||||
|_| error_container(),
|
||||
ContrastCurve::new(3.0, 4.5, 7.0, 11.0)
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: primary_fixed(),
|
||||
role_b: primary_fixed_dim(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: primary_fixed(),
|
||||
role_b: primary_fixed_dim(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_primary_fixed,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 100.0, 10.0),
|
||||
None,
|
||||
Some(Box::new(|_| primary_fixed_dim())),
|
||||
Some(Box::new(|_| primary_fixed())),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_primary_fixed_variant,
|
||||
|s: &DynamicScheme| s.primary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 90.0, 30.0),
|
||||
None,
|
||||
Some(Box::new(|_| primary_fixed_dim())),
|
||||
Some(Box::new(|_| primary_fixed())),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: secondary_fixed(),
|
||||
role_b: secondary_fixed_dim(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: secondary_fixed(),
|
||||
role_b: secondary_fixed_dim(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_secondary_fixed,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|_| 10.0,
|
||||
None,
|
||||
Some(Box::new(|_| secondary_fixed_dim())),
|
||||
Some(Box::new(|_| secondary_fixed())),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_secondary_fixed_variant,
|
||||
|s: &DynamicScheme| s.secondary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 25.0, 30.0),
|
||||
None,
|
||||
Some(Box::new(|_| secondary_fixed_dim())),
|
||||
Some(Box::new(|_| secondary_fixed())),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: tertiary_fixed(),
|
||||
role_b: tertiary_fixed_dim(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
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: tertiary_fixed(),
|
||||
role_b: tertiary_fixed_dim(),
|
||||
delta: 10.0,
|
||||
polarity: TonePolarity::Lighter,
|
||||
togather: true,
|
||||
}))
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_tertiary_fixed,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 100.0, 10.0),
|
||||
None,
|
||||
Some(Box::new(|_| tertiary_fixed_dim())),
|
||||
Some(Box::new(|_| tertiary_fixed())),
|
||||
Some(ContrastCurve::new(4.5, 7.0, 11.0, 21.0)),
|
||||
None
|
||||
);
|
||||
dynamic_gen!(
|
||||
on_tertiary_fixed_variant,
|
||||
|s: &DynamicScheme| s.tertiary_palette.clone(),
|
||||
|s: &DynamicScheme| cond!(is_monochrome(s), 90.0, 30.0),
|
||||
None,
|
||||
Some(Box::new(|_| tertiary_fixed_dim())),
|
||||
Some(Box::new(|_| tertiary_fixed())),
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None
|
||||
);
|
||||
|
||||
pub fn custom(name: String) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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(|_| inverse_surface())),
|
||||
None,
|
||||
Some(ContrastCurve::new(3.0, 4.5, 7.0, 11.0)),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn custom_fixed(name: String) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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) -> DynamicColor {
|
||||
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,
|
||||
)
|
||||
}
|
187
color-module/src/schemes/material_design_3_dynamic/mod.rs
Normal file
187
color-module/src/schemes/material_design_3_dynamic/mod.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
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, 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_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_3::MaterialDesign3Scheme;
|
||||
use material_design_3_dynamic::{build_baseline, build_dynamic_scheme, Variant};
|
||||
use q_style::{QScheme, SchemeSetting};
|
||||
use swatch_style::{SwatchEntry, SwatchSchemeSetting};
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
@ -10,6 +11,7 @@ use crate::errors;
|
|||
|
||||
pub mod material_design_2;
|
||||
pub mod material_design_3;
|
||||
pub mod material_design_3_dynamic;
|
||||
pub mod q_style;
|
||||
pub mod swatch_style;
|
||||
|
||||
|
@ -142,3 +144,48 @@ pub fn generate_swatch_scheme(
|
|||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn generate_material_design_3_dynamic_scheme(
|
||||
source_color: &str,
|
||||
error_color: Option<String>,
|
||||
variant: Variant,
|
||||
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 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),
|
||||
);
|
||||
|
||||
Ok(serde_wasm_bindgen::to_value(&(
|
||||
scheme.clone(),
|
||||
scheme.output_css_variables(),
|
||||
scheme.output_scss_variables(),
|
||||
scheme.output_javascript_object(),
|
||||
))
|
||||
.map_err(|_| errors::ColorError::UnableToAssembleOutput)?)
|
||||
}
|
||||
|
|
|
@ -106,3 +106,33 @@ pub fn triadic(color: &str) -> Result<Vec<String>, errors::ColorError> {
|
|||
format!("{:x}", srgb_p240.into_format::<u8>()),
|
||||
])
|
||||
}
|
||||
|
||||
pub fn sanitize_hue_degrees(degrees: f32) -> f32 {
|
||||
let degrees = degrees % 360.0;
|
||||
if degrees < 0.0 {
|
||||
degrees + 360.0
|
||||
} else {
|
||||
degrees
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn difference_in_degrees(a: f32, b: f32) -> f32 {
|
||||
180.0 - ((a - b).abs() - 180.0).abs()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn rotation_direction(from: f32, to: f32) -> f32 {
|
||||
let difference = sanitize_hue_degrees(to - from);
|
||||
if difference <= 180.0 {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn harmonize_hue(design_hue: f32, source_hue: f32) -> f32 {
|
||||
let difference = difference_in_degrees(design_hue, source_hue);
|
||||
let rotation_degrees = (difference * 0.5).min(15.0);
|
||||
sanitize_hue_degrees(design_hue + rotation_degrees * rotation_direction(design_hue, source_hue))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user