use frust::{Brightness, Color, Theme};
use super::color::{MaterialSemanticColors, semantic_dark, semantic_light};
pub const FALLBACK_EMPHASIS: Color = Color::from_rgb8(0x67, 0x50, 0xA4);
pub const FALLBACK_DANGER: Color = Color::from_rgb8(0xB3, 0x26, 0x1E);
#[derive(Clone, Debug, PartialEq)]
pub struct MaterialTokens {
pub light: MaterialSemanticColors,
pub dark: MaterialSemanticColors,
}
impl MaterialTokens {
pub fn material() -> Self {
Self {
light: semantic_light(),
dark: semantic_dark(),
}
}
pub fn colors(&self, brightness: Brightness) -> &MaterialSemanticColors {
match brightness {
Brightness::Light => &self.light,
Brightness::Dark => &self.dark,
}
}
pub fn resolve_emphasis(explicit: Option<Color>, theme: Option<&Theme>) -> Color {
if let Some(color) = explicit {
return color;
}
match theme.and_then(|t| t.extension::<MaterialTokens>().map(|x| (x, t.brightness))) {
Some((tokens, brightness)) => tokens.colors(brightness).emphasis,
None => FALLBACK_EMPHASIS,
}
}
pub fn resolve_danger(explicit: Option<Color>, theme: Option<&Theme>) -> Color {
if let Some(color) = explicit {
return color;
}
match theme.and_then(|t| t.extension::<MaterialTokens>().map(|x| (x, t.brightness))) {
Some((tokens, brightness)) => tokens.colors(brightness).danger,
None => FALLBACK_DANGER,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn material_tokens_attach_round_trips_from_baseline() {
let theme = super::super::baseline();
let tokens = theme
.extension::<MaterialTokens>()
.expect("baseline() attaches MaterialTokens");
assert_eq!(
tokens.colors(Brightness::Light).emphasis,
tokens.light.emphasis
);
assert_ne!(
tokens.colors(Brightness::Light).emphasis,
tokens.colors(Brightness::Dark).emphasis
);
}
#[test]
fn material_matches_the_hand_baked_semantic_tables() {
assert_eq!(MaterialTokens::material().light, semantic_light());
assert_eq!(MaterialTokens::material().dark, semantic_dark());
}
#[test]
fn colors_selects_by_brightness() {
let tokens = MaterialTokens::material();
assert_eq!(tokens.colors(Brightness::Light), &tokens.light);
assert_eq!(tokens.colors(Brightness::Dark), &tokens.dark);
}
#[test]
fn emphasis_precedence_is_explicit_then_extension_then_fallback() {
let explicit = Color::from_rgb8(0x00, 0x00, 0xFF);
let theme = super::super::baseline();
assert_eq!(
MaterialTokens::resolve_emphasis(Some(explicit), Some(&theme)),
explicit
);
assert_eq!(
MaterialTokens::resolve_emphasis(None, Some(&theme)),
MaterialTokens::material().colors(theme.brightness).emphasis
);
assert_eq!(
MaterialTokens::resolve_emphasis(None, None),
FALLBACK_EMPHASIS
);
}
#[test]
fn emphasis_follows_the_theme_s_brightness() {
let light = super::super::baseline().with_brightness(Brightness::Light);
let dark = super::super::baseline().with_brightness(Brightness::Dark);
assert_eq!(
MaterialTokens::resolve_emphasis(None, Some(&light)),
MaterialTokens::material().light.emphasis
);
assert_eq!(
MaterialTokens::resolve_emphasis(None, Some(&dark)),
MaterialTokens::material().dark.emphasis
);
}
#[test]
fn danger_precedence_is_explicit_then_extension_then_fallback() {
let explicit = Color::from_rgb8(0x00, 0xFF, 0x00);
let theme = super::super::baseline();
assert_eq!(
MaterialTokens::resolve_danger(Some(explicit), Some(&theme)),
explicit
);
assert_eq!(
MaterialTokens::resolve_danger(None, Some(&theme)),
MaterialTokens::material().colors(theme.brightness).danger
);
assert_eq!(MaterialTokens::resolve_danger(None, None), FALLBACK_DANGER);
}
#[test]
fn a_theme_with_the_extension_cleared_falls_through_to_the_fallbacks() {
let bare = Theme::neutral();
assert!(bare.extension::<MaterialTokens>().is_none());
assert_eq!(
MaterialTokens::resolve_emphasis(None, Some(&bare)),
FALLBACK_EMPHASIS,
"a non-Material theme resolves the fallback emphasis color"
);
assert_eq!(
MaterialTokens::resolve_danger(None, Some(&bare)),
FALLBACK_DANGER,
"a non-Material theme resolves the fallback danger color"
);
}
}