use peniko::Color;
use crate::style::Shadow;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ChromeText {
#[default]
Sm,
Md,
Lg,
Xl,
}
impl ChromeText {
pub const fn px(self) -> f32 {
match self {
Self::Sm => 11.0,
Self::Md => 12.0,
Self::Lg => 13.0,
Self::Xl => 14.0,
}
}
pub const fn tracking(self) -> f32 {
match self {
Self::Sm => 0.005,
Self::Md => 0.0,
Self::Lg => -0.0025,
Self::Xl => -0.001,
}
}
pub fn line_height(self) -> f32 {
let lh = if matches!(self, Self::Sm | Self::Md) {
16.0
} else {
24.0
};
lh / self.px()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChromeElevation {
Popover,
Modal,
Thumb,
}
fn black(alpha: f32) -> Color {
Color::new([0.0, 0.0, 0.0, alpha])
}
fn ring(alpha: f32) -> Shadow {
Shadow {
dx: 0.0,
dy: 0.0,
blur: 0.0,
spread: 0.5,
color: black(alpha),
}
}
fn drop(dy: f32, blur: f32, alpha: f32) -> Shadow {
Shadow {
dx: 0.0,
dy,
blur,
spread: 0.0,
color: black(alpha),
}
}
impl ChromeElevation {
pub fn shadows(self) -> Vec<Shadow> {
match self {
Self::Popover => vec![drop(5.0, 17.0, 0.20), drop(2.0, 7.0, 0.15), ring(0.10)],
Self::Modal => vec![drop(2.0, 14.0, 0.15), ring(0.20)],
Self::Thumb => vec![drop(1.0, 3.0, 0.10), drop(3.0, 8.0, 0.10), ring(0.18)],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chrome_text_is_the_dense_11_to_14_register() {
assert_eq!(ChromeText::Sm.px(), 11.0);
assert_eq!(ChromeText::Xl.px(), 14.0);
assert!(ChromeText::Sm.tracking() > 0.0);
assert!(ChromeText::Lg.tracking() < 0.0);
assert!((ChromeText::Sm.line_height() * 11.0 - 16.0).abs() < 1e-4);
}
#[test]
fn chrome_elevation_is_two_drops_plus_a_hairline_ring() {
let pop = ChromeElevation::Popover.shadows();
assert_eq!(pop.len(), 3);
let r = pop[2];
assert_eq!(r.blur, 0.0);
assert!((r.spread - 0.5).abs() < 1e-6);
assert!(pop[0].blur > 0.0 && pop[0].dy > 0.0);
for s in pop {
let [r, g, b, _] = s.color.components;
assert!(r == 0.0 && g == 0.0 && b == 0.0);
}
assert_eq!(ChromeElevation::Modal.shadows().len(), 2);
}
}