use mcu_hct::Hct;
use mcu_palettes::TonalPalette;
use crate::dynamic_scheme_palettes::{get_palettes_spec, maybe_fallback_spec_version};
use crate::Variant;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Platform {
#[default]
Phone,
Watch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SpecVersion {
#[default]
Spec2021,
Spec2025,
}
#[derive(Clone)]
pub struct DynamicSchemeOptions {
pub source_color_hct: Hct,
pub variant: Variant,
pub contrast_level: f64,
pub is_dark: bool,
pub platform: Option<Platform>,
pub spec_version: Option<SpecVersion>,
pub primary_palette: Option<TonalPalette>,
pub secondary_palette: Option<TonalPalette>,
pub tertiary_palette: Option<TonalPalette>,
pub neutral_palette: Option<TonalPalette>,
pub neutral_variant_palette: Option<TonalPalette>,
pub error_palette: Option<TonalPalette>,
}
impl DynamicSchemeOptions {
pub fn new(
source_color_hct: Hct,
variant: Variant,
contrast_level: f64,
is_dark: bool,
) -> Self {
DynamicSchemeOptions {
source_color_hct,
variant,
contrast_level,
is_dark,
platform: None,
spec_version: None,
primary_palette: None,
secondary_palette: None,
tertiary_palette: None,
neutral_palette: None,
neutral_variant_palette: None,
error_palette: None,
}
}
}
#[derive(Clone)]
pub struct DynamicScheme {
pub source_color_hct: Hct,
pub source_color_argb: u32,
pub variant: Variant,
pub contrast_level: f64,
pub is_dark: bool,
pub platform: Platform,
pub spec_version: SpecVersion,
pub primary_palette: TonalPalette,
pub secondary_palette: TonalPalette,
pub tertiary_palette: TonalPalette,
pub neutral_palette: TonalPalette,
pub neutral_variant_palette: TonalPalette,
pub error_palette: TonalPalette,
}
impl DynamicScheme {
pub fn new(options: DynamicSchemeOptions) -> Self {
let source_color_argb = options.source_color_hct.to_int();
let platform = options.platform.unwrap_or_default();
let requested_spec = options.spec_version.unwrap_or_default();
let spec_version = maybe_fallback_spec_version(requested_spec, options.variant);
let spec = get_palettes_spec(spec_version);
let primary_palette = options.primary_palette.unwrap_or_else(|| {
spec.get_primary_palette(
options.variant,
&options.source_color_hct,
options.is_dark,
platform,
options.contrast_level,
)
});
let secondary_palette = options.secondary_palette.unwrap_or_else(|| {
spec.get_secondary_palette(
options.variant,
&options.source_color_hct,
options.is_dark,
platform,
options.contrast_level,
)
});
let tertiary_palette = options.tertiary_palette.unwrap_or_else(|| {
spec.get_tertiary_palette(
options.variant,
&options.source_color_hct,
options.is_dark,
platform,
options.contrast_level,
)
});
let neutral_palette = options.neutral_palette.unwrap_or_else(|| {
spec.get_neutral_palette(
options.variant,
&options.source_color_hct,
options.is_dark,
platform,
options.contrast_level,
)
});
let neutral_variant_palette = options.neutral_variant_palette.unwrap_or_else(|| {
spec.get_neutral_variant_palette(
options.variant,
&options.source_color_hct,
options.is_dark,
platform,
options.contrast_level,
)
});
let error_palette = options.error_palette.unwrap_or_else(|| {
spec.get_error_palette(
options.variant,
&options.source_color_hct,
options.is_dark,
platform,
options.contrast_level,
)
.unwrap_or_else(|| TonalPalette::from_hue_and_chroma(25.0, 84.0))
});
DynamicScheme {
source_color_hct: options.source_color_hct,
source_color_argb,
variant: options.variant,
contrast_level: options.contrast_level,
is_dark: options.is_dark,
platform,
spec_version,
primary_palette,
secondary_palette,
tertiary_palette,
neutral_palette,
neutral_variant_palette,
error_palette,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_platform_default() {
assert_eq!(Platform::default(), Platform::Phone);
}
#[test]
fn test_spec_version_default() {
assert_eq!(SpecVersion::default(), SpecVersion::Spec2021);
}
#[test]
fn test_dynamic_scheme_options_new() {
let hct = Hct::from_int(0xFF0000FF); let options = DynamicSchemeOptions::new(hct, Variant::TonalSpot, 0.0, false);
assert_eq!(options.variant, Variant::TonalSpot);
assert_eq!(options.contrast_level, 0.0);
assert!(!options.is_dark);
assert!(options.platform.is_none());
assert!(options.spec_version.is_none());
}
#[test]
fn test_dynamic_scheme_new_light_mode() {
let hct = Hct::from_int(0xFF0000FF); let options = DynamicSchemeOptions::new(hct, Variant::TonalSpot, 0.0, false);
let scheme = DynamicScheme::new(options);
assert_eq!(scheme.variant, Variant::TonalSpot);
assert_eq!(scheme.contrast_level, 0.0);
assert!(!scheme.is_dark);
assert_eq!(scheme.platform, Platform::Phone);
assert_eq!(scheme.spec_version, SpecVersion::Spec2021);
assert_eq!(scheme.source_color_argb, 0xFF0000FF);
}
#[test]
fn test_dynamic_scheme_new_dark_mode() {
let hct = Hct::from_int(0xFFFF0000); let options = DynamicSchemeOptions::new(hct, Variant::Vibrant, 0.0, true);
let scheme = DynamicScheme::new(options);
assert_eq!(scheme.variant, Variant::Vibrant);
assert!(scheme.is_dark);
}
#[test]
fn test_dynamic_scheme_contrast_levels() {
let hct = Hct::from_int(0xFF00FF00);
let low_contrast = DynamicScheme::new(DynamicSchemeOptions::new(
hct,
Variant::TonalSpot,
-1.0,
false,
));
assert_eq!(low_contrast.contrast_level, -1.0);
let normal_contrast = DynamicScheme::new(DynamicSchemeOptions::new(
hct,
Variant::TonalSpot,
0.0,
false,
));
assert_eq!(normal_contrast.contrast_level, 0.0);
let high_contrast = DynamicScheme::new(DynamicSchemeOptions::new(
hct,
Variant::TonalSpot,
1.0,
false,
));
assert_eq!(high_contrast.contrast_level, 1.0);
}
#[test]
fn test_dynamic_scheme_custom_palettes() {
let hct = Hct::from_int(0xFF0000FF);
let custom_primary = TonalPalette::from_hue_and_chroma(120.0, 50.0);
let mut options = DynamicSchemeOptions::new(hct, Variant::TonalSpot, 0.0, false);
options.primary_palette = Some(custom_primary.clone());
let scheme = DynamicScheme::new(options);
assert_eq!(scheme.primary_palette.hue(), custom_primary.hue());
assert_eq!(scheme.primary_palette.chroma(), custom_primary.chroma());
}
#[test]
fn test_dynamic_scheme_all_variants() {
let hct = Hct::from_int(0xFF0000FF);
let variants = [
Variant::Monochrome,
Variant::Neutral,
Variant::TonalSpot,
Variant::Vibrant,
Variant::Expressive,
];
for variant in &variants {
let options = DynamicSchemeOptions::new(hct, *variant, 0.0, false);
let scheme = DynamicScheme::new(options);
assert_eq!(scheme.variant, *variant);
}
}
#[test]
fn test_dynamic_scheme_clone() {
let hct = Hct::from_int(0xFF0000FF);
let options = DynamicSchemeOptions::new(hct, Variant::TonalSpot, 0.0, false);
let scheme1 = DynamicScheme::new(options);
let scheme2 = scheme1.clone();
assert_eq!(scheme1.source_color_argb, scheme2.source_color_argb);
assert_eq!(scheme1.variant, scheme2.variant);
assert_eq!(scheme1.is_dark, scheme2.is_dark);
}
#[test]
fn test_platform_variants() {
assert_eq!(Platform::Phone, Platform::Phone);
assert_eq!(Platform::Watch, Platform::Watch);
assert_ne!(Platform::Phone, Platform::Watch);
}
#[test]
fn test_spec_version_variants() {
assert_eq!(SpecVersion::Spec2021, SpecVersion::Spec2021);
assert_eq!(SpecVersion::Spec2025, SpecVersion::Spec2025);
assert_ne!(SpecVersion::Spec2021, SpecVersion::Spec2025);
}
}