use crate::*; use rand::{thread_rng, distributions::uniform::UniformSampler}; pub use ratatui::prelude::Color; pub trait HasColor { fn color (&self) -> ItemColor; } #[macro_export] macro_rules! has_color { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { fn color (&$self) -> ItemColor { $cb } } } } /// A color in OKHSL and RGB representations. #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor { pub okhsl: Okhsl, pub rgb: Color, } /// A color in OKHSL and RGB with lighter and darker variants. #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemPalette { pub base: ItemColor, pub light: ItemColor, pub lighter: ItemColor, pub lightest: ItemColor, pub dark: ItemColor, pub darker: ItemColor, pub darkest: ItemColor, } from!(|okhsl: Okhsl|ItemColor = Self { okhsl, rgb: okhsl_to_rgb(okhsl) }); from!(|rgb: Color|ItemColor = Self { rgb, okhsl: rgb_to_okhsl(rgb) }); // A single color within item theme parameters, in OKHSL and RGB representations. impl ItemColor { pub fn random () -> Self { let mut rng = thread_rng(); let lo = Okhsl::new(-180.0, 0.01, 0.25); let hi = Okhsl::new( 180.0, 0.9, 0.5); UniformOkhsl::new(lo, hi).sample(&mut rng).into() } pub fn random_dark () -> Self { let mut rng = thread_rng(); let lo = Okhsl::new(-180.0, 0.025, 0.075); let hi = Okhsl::new( 180.0, 0.5, 0.150); UniformOkhsl::new(lo, hi).sample(&mut rng).into() } pub fn random_near (color: Self, distance: f32) -> Self { color.mix(Self::random(), distance) } pub fn mix (&self, other: Self, distance: f32) -> Self { if distance > 1.0 { panic!("color mixing takes distance between 0.0 and 1.0"); } self.okhsl.mix(other.okhsl, distance).into() } } from!(|base: Color|ItemPalette = Self::from(ItemColor::from(base))); from!(|base: ItemColor|ItemPalette = { let mut light = base.okhsl; light.lightness = (light.lightness * 1.3).min(Okhsl::::max_lightness()); let mut lighter = light; lighter.lightness = (lighter.lightness * 1.3).min(Okhsl::::max_lightness()); let mut lightest = lighter; lightest.lightness = (lightest.lightness * 1.3).min(Okhsl::::max_lightness()); let mut dark = base.okhsl; dark.lightness = (dark.lightness * 0.75).max(Okhsl::::min_lightness()); dark.saturation = (dark.saturation * 0.75).max(Okhsl::::min_saturation()); let mut darker = dark; darker.lightness = (darker.lightness * 0.66).max(Okhsl::::min_lightness()); darker.saturation = (darker.saturation * 0.66).max(Okhsl::::min_saturation()); let mut darkest = darker; darkest.lightness = (darkest.lightness * 0.50).max(Okhsl::::min_lightness()); darkest.saturation = (darkest.saturation * 0.50).max(Okhsl::::min_saturation()); Self { base, light: light.into(), lighter: lighter.into(), lightest: lightest.into(), dark: dark.into(), darker: darker.into(), darkest: darkest.into(), } }); impl ItemPalette { pub fn random () -> Self { ItemColor::random().into() } pub fn random_near (color: Self, distance: f32) -> Self { color.base.mix(ItemColor::random(), distance).into() } } pub fn okhsl_to_rgb (color: Okhsl) -> Color { let Srgb { red, green, blue, .. }: Srgb = Srgb::from_color_unclamped(color); Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,) } pub fn rgb_to_okhsl (color: Color) -> Okhsl { if let Color::Rgb(r, g, b) = color { Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0)) } else { unreachable!("only Color::Rgb is supported") } }