use super::palette::CorePalette;
use bevy::prelude::Color;
#[derive(Debug, Clone)]
pub struct MaterialColorScheme {
pub primary: Color,
pub on_primary: Color,
pub primary_container: Color,
pub on_primary_container: Color,
pub secondary: Color,
pub on_secondary: Color,
pub secondary_container: Color,
pub on_secondary_container: Color,
pub tertiary: Color,
pub on_tertiary: Color,
pub tertiary_container: Color,
pub on_tertiary_container: Color,
pub error: Color,
pub on_error: Color,
pub error_container: Color,
pub on_error_container: Color,
pub surface: Color,
pub surface_bright: Color,
pub surface_dim: Color,
pub on_surface: Color,
pub on_surface_variant: Color,
pub surface_container_lowest: Color,
pub surface_container_low: Color,
pub surface_container: Color,
pub surface_container_high: Color,
pub surface_container_highest: Color,
pub outline: Color,
pub outline_variant: Color,
pub inverse_surface: Color,
pub inverse_on_surface: Color,
pub inverse_primary: Color,
pub primary_fixed: Color,
pub primary_fixed_dim: Color,
pub on_primary_fixed: Color,
pub on_primary_fixed_variant: Color,
pub secondary_fixed: Color,
pub secondary_fixed_dim: Color,
pub on_secondary_fixed: Color,
pub on_secondary_fixed_variant: Color,
pub tertiary_fixed: Color,
pub tertiary_fixed_dim: Color,
pub on_tertiary_fixed: Color,
pub on_tertiary_fixed_variant: Color,
pub scrim: Color,
pub shadow: Color,
}
impl Default for MaterialColorScheme {
fn default() -> Self {
Self::dark_from_argb(0xFF6750A4)
}
}
impl MaterialColorScheme {
pub fn dark_from_argb(seed: u32) -> Self {
let mut palette = CorePalette::from_argb(seed);
palette.cache_all();
Self::dark_from_palette(&mut palette)
}
pub fn light_from_argb(seed: u32) -> Self {
let mut palette = CorePalette::from_argb(seed);
palette.cache_all();
Self::light_from_palette(&mut palette)
}
pub fn dark_from_bevy_color(color: Color) -> Self {
let mut palette = CorePalette::from_bevy_color(color);
palette.cache_all();
Self::dark_from_palette(&mut palette)
}
pub fn light_from_bevy_color(color: Color) -> Self {
let mut palette = CorePalette::from_bevy_color(color);
palette.cache_all();
Self::light_from_palette(&mut palette)
}
pub fn dark_from_palette(p: &mut CorePalette) -> Self {
Self {
primary: argb_to_color(p.primary.tone(80)),
on_primary: argb_to_color(p.primary.tone(20)),
primary_container: argb_to_color(p.primary.tone(30)),
on_primary_container: argb_to_color(p.primary.tone(90)),
secondary: argb_to_color(p.secondary.tone(80)),
on_secondary: argb_to_color(p.secondary.tone(20)),
secondary_container: argb_to_color(p.secondary.tone(30)),
on_secondary_container: argb_to_color(p.secondary.tone(90)),
tertiary: argb_to_color(p.tertiary.tone(80)),
on_tertiary: argb_to_color(p.tertiary.tone(20)),
tertiary_container: argb_to_color(p.tertiary.tone(30)),
on_tertiary_container: argb_to_color(p.tertiary.tone(90)),
error: argb_to_color(p.error.tone(80)),
on_error: argb_to_color(p.error.tone(20)),
error_container: argb_to_color(p.error.tone(30)),
on_error_container: argb_to_color(p.error.tone(90)),
surface: argb_to_color(p.neutral.tone(6)),
surface_bright: argb_to_color(p.neutral.tone(24)),
surface_dim: argb_to_color(p.neutral.tone(6)),
on_surface: argb_to_color(p.neutral.tone(90)),
on_surface_variant: argb_to_color(p.neutral_variant.tone(80)),
surface_container_lowest: argb_to_color(p.neutral.tone(4)),
surface_container_low: argb_to_color(p.neutral.tone(10)),
surface_container: argb_to_color(p.neutral.tone(12)),
surface_container_high: argb_to_color(p.neutral.tone(17)),
surface_container_highest: argb_to_color(p.neutral.tone(22)),
outline: argb_to_color(p.neutral_variant.tone(60)),
outline_variant: argb_to_color(p.neutral_variant.tone(30)),
inverse_surface: argb_to_color(p.neutral.tone(90)),
inverse_on_surface: argb_to_color(p.neutral.tone(20)),
inverse_primary: argb_to_color(p.primary.tone(40)),
primary_fixed: argb_to_color(p.primary.tone(90)),
primary_fixed_dim: argb_to_color(p.primary.tone(80)),
on_primary_fixed: argb_to_color(p.primary.tone(10)),
on_primary_fixed_variant: argb_to_color(p.primary.tone(30)),
secondary_fixed: argb_to_color(p.secondary.tone(90)),
secondary_fixed_dim: argb_to_color(p.secondary.tone(80)),
on_secondary_fixed: argb_to_color(p.secondary.tone(10)),
on_secondary_fixed_variant: argb_to_color(p.secondary.tone(30)),
tertiary_fixed: argb_to_color(p.tertiary.tone(90)),
tertiary_fixed_dim: argb_to_color(p.tertiary.tone(80)),
on_tertiary_fixed: argb_to_color(p.tertiary.tone(10)),
on_tertiary_fixed_variant: argb_to_color(p.tertiary.tone(30)),
scrim: Color::BLACK,
shadow: Color::BLACK,
}
}
pub fn light_from_palette(p: &mut CorePalette) -> Self {
Self {
primary: argb_to_color(p.primary.tone(40)),
on_primary: argb_to_color(p.primary.tone(100)),
primary_container: argb_to_color(p.primary.tone(90)),
on_primary_container: argb_to_color(p.primary.tone(10)),
secondary: argb_to_color(p.secondary.tone(40)),
on_secondary: argb_to_color(p.secondary.tone(100)),
secondary_container: argb_to_color(p.secondary.tone(90)),
on_secondary_container: argb_to_color(p.secondary.tone(10)),
tertiary: argb_to_color(p.tertiary.tone(40)),
on_tertiary: argb_to_color(p.tertiary.tone(100)),
tertiary_container: argb_to_color(p.tertiary.tone(90)),
on_tertiary_container: argb_to_color(p.tertiary.tone(10)),
error: argb_to_color(p.error.tone(40)),
on_error: argb_to_color(p.error.tone(100)),
error_container: argb_to_color(p.error.tone(90)),
on_error_container: argb_to_color(p.error.tone(10)),
surface: argb_to_color(p.neutral.tone(98)),
surface_bright: argb_to_color(p.neutral.tone(98)),
surface_dim: argb_to_color(p.neutral.tone(87)),
on_surface: argb_to_color(p.neutral.tone(10)),
on_surface_variant: argb_to_color(p.neutral_variant.tone(30)),
surface_container_lowest: argb_to_color(p.neutral.tone(100)),
surface_container_low: argb_to_color(p.neutral.tone(96)),
surface_container: argb_to_color(p.neutral.tone(94)),
surface_container_high: argb_to_color(p.neutral.tone(92)),
surface_container_highest: argb_to_color(p.neutral.tone(90)),
outline: argb_to_color(p.neutral_variant.tone(50)),
outline_variant: argb_to_color(p.neutral_variant.tone(80)),
inverse_surface: argb_to_color(p.neutral.tone(20)),
inverse_on_surface: argb_to_color(p.neutral.tone(95)),
inverse_primary: argb_to_color(p.primary.tone(80)),
primary_fixed: argb_to_color(p.primary.tone(90)),
primary_fixed_dim: argb_to_color(p.primary.tone(80)),
on_primary_fixed: argb_to_color(p.primary.tone(10)),
on_primary_fixed_variant: argb_to_color(p.primary.tone(30)),
secondary_fixed: argb_to_color(p.secondary.tone(90)),
secondary_fixed_dim: argb_to_color(p.secondary.tone(80)),
on_secondary_fixed: argb_to_color(p.secondary.tone(10)),
on_secondary_fixed_variant: argb_to_color(p.secondary.tone(30)),
tertiary_fixed: argb_to_color(p.tertiary.tone(90)),
tertiary_fixed_dim: argb_to_color(p.tertiary.tone(80)),
on_tertiary_fixed: argb_to_color(p.tertiary.tone(10)),
on_tertiary_fixed_variant: argb_to_color(p.tertiary.tone(30)),
scrim: Color::BLACK,
shadow: Color::BLACK,
}
}
}
fn argb_to_color(argb: u32) -> Color {
let r = ((argb >> 16) & 0xFF) as f32 / 255.0;
let g = ((argb >> 8) & 0xFF) as f32 / 255.0;
let b = (argb & 0xFF) as f32 / 255.0;
Color::srgb(r, g, b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dark_scheme_generation() {
let scheme = MaterialColorScheme::dark_from_argb(0xFF6750A4);
let primary_srgba = scheme.primary.to_srgba();
let on_primary_srgba = scheme.on_primary.to_srgba();
let primary_lum =
0.299 * primary_srgba.red + 0.587 * primary_srgba.green + 0.114 * primary_srgba.blue;
let on_primary_lum = 0.299 * on_primary_srgba.red
+ 0.587 * on_primary_srgba.green
+ 0.114 * on_primary_srgba.blue;
assert!(
primary_lum > on_primary_lum,
"Primary should be lighter than on_primary in dark theme"
);
}
#[test]
fn test_light_scheme_generation() {
let scheme = MaterialColorScheme::light_from_argb(0xFF6750A4);
let primary_srgba = scheme.primary.to_srgba();
let on_primary_srgba = scheme.on_primary.to_srgba();
let primary_lum =
0.299 * primary_srgba.red + 0.587 * primary_srgba.green + 0.114 * primary_srgba.blue;
let on_primary_lum = 0.299 * on_primary_srgba.red
+ 0.587 * on_primary_srgba.green
+ 0.114 * on_primary_srgba.blue;
assert!(
on_primary_lum > primary_lum,
"on_primary should be lighter than primary in light theme"
);
}
#[test]
fn test_surface_hierarchy_dark() {
let scheme = MaterialColorScheme::dark_from_argb(0xFF6750A4);
fn luminance(c: Color) -> f32 {
let srgba = c.to_srgba();
0.299 * srgba.red + 0.587 * srgba.green + 0.114 * srgba.blue
}
assert!(
luminance(scheme.surface_container_lowest) < luminance(scheme.surface_container_low)
);
assert!(luminance(scheme.surface_container_low) < luminance(scheme.surface_container));
assert!(luminance(scheme.surface_container) < luminance(scheme.surface_container_high));
assert!(
luminance(scheme.surface_container_high) < luminance(scheme.surface_container_highest)
);
}
#[test]
fn test_error_colors() {
let scheme = MaterialColorScheme::dark_from_argb(0xFF6750A4);
let error_srgba = scheme.error.to_srgba();
assert!(
error_srgba.red > error_srgba.blue,
"Error should be more red than blue"
);
}
#[test]
fn test_from_bevy_color() {
let seed = Color::srgb(0.4, 0.31, 0.64);
let scheme = MaterialColorScheme::dark_from_bevy_color(seed);
let primary_srgba = scheme.primary.to_srgba();
assert!(primary_srgba.red >= 0.0 && primary_srgba.red <= 1.0);
assert!(primary_srgba.green >= 0.0 && primary_srgba.green <= 1.0);
assert!(primary_srgba.blue >= 0.0 && primary_srgba.blue <= 1.0);
}
}