mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
104 lines
3.8 KiB
Rust
104 lines
3.8 KiB
Rust
use crate::*;
|
|
use rand::{thread_rng, distributions::uniform::UniformSampler};
|
|
|
|
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<f32>,
|
|
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<f32>|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::<f32>::max_lightness());
|
|
let mut lighter = light;
|
|
lighter.lightness = (lighter.lightness * 1.3).min(Okhsl::<f32>::max_lightness());
|
|
let mut lightest = base.okhsl;
|
|
lightest.lightness = 0.95;
|
|
|
|
let mut dark = base.okhsl;
|
|
dark.lightness = (dark.lightness * 0.75).max(Okhsl::<f32>::min_lightness());
|
|
dark.saturation = (dark.saturation * 0.75).max(Okhsl::<f32>::min_saturation());
|
|
let mut darker = dark;
|
|
darker.lightness = (darker.lightness * 0.66).max(Okhsl::<f32>::min_lightness());
|
|
darker.saturation = (darker.saturation * 0.66).max(Okhsl::<f32>::min_saturation());
|
|
let mut darkest = darker;
|
|
darkest.lightness = 0.1;
|
|
darkest.saturation = (darkest.saturation * 0.50).max(Okhsl::<f32>::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<f32>) -> Color {
|
|
let Srgb { red, green, blue, .. }: Srgb<f32> = 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<f32> {
|
|
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")
|
|
}
|
|
}
|