use crate::{Rgb, mix};
#[allow(unused_imports)]
use crate::DISTINCT;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Emphasis {
Full,
Secondary,
Muted,
}
impl Emphasis {
#[must_use]
pub const fn ratio(self) -> f32 {
match self {
Self::Full => 0.0,
Self::Secondary => 0.12,
Self::Muted => 0.42,
}
}
#[must_use]
pub const fn suffix(self) -> Option<&'static str> {
match self {
Self::Full => None,
Self::Secondary => Some("-secondary"),
Self::Muted => Some("-muted"),
}
}
#[must_use]
pub fn token(self, token: &str) -> String {
match self.suffix() {
Some(suffix) => format!("{token}{suffix}"),
None => token.to_string(),
}
}
}
pub const STEP_FLOOR: f32 = 1.21;
#[must_use]
pub fn tonal(base: Rgb, ground: Rgb, ratio: f32) -> Rgb {
mix(base, ground, ratio.clamp(0.0, 1.0))
}
#[must_use]
pub fn emphasized(base: Rgb, ground: Rgb, emphasis: Emphasis) -> Rgb {
tonal(base, ground, emphasis.ratio())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_tonal_step_lands_between_its_base_and_its_ground() {
let ink = Rgb::from_hex("#d8dee9").unwrap();
let page = Rgb::from_hex("#2e3440").unwrap();
for step in [Emphasis::Full, Emphasis::Secondary, Emphasis::Muted] {
let out = emphasized(ink, page, step).to_oklab().l;
assert!(
out <= ink.to_oklab().l && out >= page.to_oklab().l,
"{step:?} left the interval between the ink and the page"
);
}
assert_eq!(emphasized(ink, page, Emphasis::Full).to_hex(), ink.to_hex());
}
#[test]
fn tonal_steps_compose_rather_than_compound() {
let ink = Rgb::from_hex("#d8dee9").unwrap();
let page = Rgb::from_hex("#2e3440").unwrap();
let (a, b) = (0.12f32, 0.42f32);
let twice = tonal(tonal(ink, page, a), page, b);
let once = tonal(ink, page, a + b - a * b);
let (x, y) = (twice.tuple(), once.tuple());
for (l, r) in [(x.0, y.0), (x.1, y.1), (x.2, y.2)] {
assert!(l.abs_diff(r) <= 1, "{twice:?} is not {once:?}");
}
}
#[test]
fn a_ratio_outside_the_interval_is_clamped_rather_than_extrapolated() {
let ink = Rgb::from_hex("#d8dee9").unwrap();
let page = Rgb::from_hex("#2e3440").unwrap();
assert_eq!(tonal(ink, page, -1.0).to_hex(), ink.to_hex());
assert_eq!(tonal(ink, page, 2.0).to_hex(), page.to_hex());
}
#[test]
fn a_derived_token_key_is_the_family_plus_the_step() {
assert_eq!(Emphasis::Muted.token("content"), "content-muted");
assert_eq!(Emphasis::Secondary.token("content"), "content-secondary");
assert_eq!(Emphasis::Full.token("content"), "content");
assert_eq!(Emphasis::Muted.token("danger"), "danger-muted");
}
}