use iced_core::Color; use lazy_static::lazy_static; use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb}; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Palette { background: Color, text: Color, primary: Color, success: Color, danger: Color, } impl Palette { pub const LIGHT: Self = Self { background: Color::WHITE, text: Color::BLACK, primary: Color::from_rgb( 0x5E as f32 / 255.0, 0x7C as f32 / 255.0, 0xE2 as f32 / 255.0, ), success: Color::from_rgb( 0x12 as f32 / 255.0, 0x66 as f32 / 255.0, 0x4F as f32 / 255.0, ), danger: Color::from_rgb( 0xC3 as f32 / 255.0, 0x42 as f32 / 255.0, 0x3F as f32 / 255.0, ), }; pub const DARK: Self = Self { background: Color::WHITE, text: Color::BLACK, primary: Color::from_rgb( 0x5E as f32 / 255.0, 0x7C as f32 / 255.0, 0xE2 as f32 / 255.0, ), success: Color::from_rgb( 0x12 as f32 / 255.0, 0x66 as f32 / 255.0, 0x4F as f32 / 255.0, ), danger: Color::from_rgb( 0xC3 as f32 / 255.0, 0x42 as f32 / 255.0, 0x3F as f32 / 255.0, ), }; } pub struct Extended { pub background: Background, pub primary: Group, pub success: Group, pub danger: Group, } lazy_static! { pub static ref EXTENDED_LIGHT: Extended = Extended::generate(Palette::LIGHT); pub static ref EXTENDED_DARK: Extended = Extended::generate(Palette::DARK); } impl Extended { pub fn generate(palette: Palette) -> Self { Self { background: Background::new(palette.background, palette.text), primary: Group::new( palette.primary, palette.background, palette.text, ), success: Group::new( palette.success, palette.background, palette.text, ), danger: Group::new( palette.danger, palette.background, palette.text, ), } } } pub struct Background { pub base: Color, pub weak: Color, pub strong: Color, pub text: Color, } impl Background { pub fn new(base: Color, text: Color) -> Self { Self { base, weak: muted(base, 0.1), strong: muted(base, 0.2), text, } } } pub struct Group { pub base: Color, pub weak: Color, pub strong: Color, pub text: Color, } impl Group { pub fn new(base: Color, background: Color, text: Color) -> Self { Self { base, weak: mix(base, background, 0.4), strong: if is_dark(background) { lighten(base, 0.1) } else { darken(base, 0.1) }, text: if is_readable(base, text) { text } else if is_dark(text) { Color::WHITE } else { Color::BLACK }, } } } fn muted(color: Color, amount: f32) -> Color { let mut hsl = to_hsl(color); hsl.lightness = if is_dark(color) { let delta = amount * (1.0 - hsl.lightness).powi(7); hsl.lightness + delta } else { let delta = amount * hsl.lightness.powi(5); hsl.lightness - delta }; from_hsl(hsl) } fn darken(color: Color, amount: f32) -> Color { let mut hsl = to_hsl(color); hsl.lightness = if hsl.lightness - amount < 0.0 { 0.0 } else { hsl.lightness - amount }; from_hsl(hsl) } fn mix(a: Color, b: Color, factor: f32) -> Color { let a_lin = Srgb::from(a).into_linear(); let b_lin = Srgb::from(b).into_linear(); let mixed = a_lin.mix(&b_lin, factor); Srgb::from_linear(mixed).into() } fn lighten(color: Color, amount: f32) -> Color { let mut hsl = to_hsl(color); hsl.lightness = if hsl.lightness + amount > 1.0 { 1.0 } else { hsl.lightness + amount }; from_hsl(hsl) } fn is_dark(color: Color) -> bool { to_hsl(color).lightness < 0.5 } fn is_readable(a: Color, b: Color) -> bool { let a_srgb = Srgb::from(a); let b_srgb = Srgb::from(b); a_srgb.has_enhanced_contrast_text(&b_srgb) } fn to_hsl(color: Color) -> Hsl { Hsl::from_color(Srgb::from(color)) } fn from_hsl(hsl: Hsl) -> Color { Srgb::from_color(hsl).into() }