#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![warn(unused_extern_crates)]
#![warn(missing_docs)]
#![recursion_limit = "256"]
use std::{fmt, ops, sync::Arc};
use zng_app_context::context_local;
use zng_layout::unit::{about_eq, about_eq_hash, AngleDegree, Factor, FactorUnits};
use zng_var::{
animation::{easing::EasingStep, Transition, Transitionable},
context_var, expr_var, impl_from_and_into_var,
types::ContextualizedVar,
IntoVar, Var, VarValue,
};
pub use zng_view_api::config::ColorScheme;
#[doc(hidden)]
pub use zng_color_proc_macros::hex_color;
pub use zng_layout::unit::{Rgba, RgbaComponent};
pub mod colors;
pub mod filter;
pub mod gradient;
pub mod web_colors;
mod mix;
pub use mix::*;
#[macro_export]
macro_rules! hex {
($($tt:tt)+) => {
$crate::hex_color!{$crate, $($tt)*}
};
}
const EPSILON: f32 = 0.00001;
const EPSILON_100: f32 = 0.001;
fn lerp_rgba_linear(mut from: Rgba, to: Rgba, factor: Factor) -> Rgba {
from.red = from.red.lerp(&to.red, factor);
from.green = from.green.lerp(&to.green, factor);
from.blue = from.blue.lerp(&to.blue, factor);
from.alpha = from.alpha.lerp(&to.alpha, factor);
from
}
pub fn lerp_rgba(from: Rgba, to: Rgba, factor: Factor) -> Rgba {
match lerp_space() {
LerpSpace::HslaChromatic => Hsla::from(from).slerp_chromatic(to.into(), factor).into(),
LerpSpace::Rgba => lerp_rgba_linear(from, to, factor),
LerpSpace::Hsla => Hsla::from(from).slerp(to.into(), factor).into(),
LerpSpace::HslaLinear => Hsla::from(from).lerp_hsla(to.into(), factor).into(),
}
}
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub struct PreMulRgba {
pub red: f32,
pub green: f32,
pub blue: f32,
pub alpha: f32,
}
impl PartialEq for PreMulRgba {
fn eq(&self, other: &Self) -> bool {
about_eq(self.red, other.red, EPSILON)
&& about_eq(self.green, other.green, EPSILON)
&& about_eq(self.blue, other.blue, EPSILON)
&& about_eq(self.alpha, other.alpha, EPSILON)
}
}
impl std::hash::Hash for PreMulRgba {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
about_eq_hash(self.red, EPSILON, state);
about_eq_hash(self.green, EPSILON, state);
about_eq_hash(self.blue, EPSILON, state);
about_eq_hash(self.alpha, EPSILON, state);
}
}
impl_from_and_into_var! {
fn from(c: Rgba) -> PreMulRgba {
PreMulRgba {
red: c.red * c.alpha,
green: c.green * c.alpha,
blue: c.blue * c.alpha,
alpha: c.alpha,
}
}
fn from(c: PreMulRgba) -> Rgba {
Rgba {
red: c.red / c.alpha,
green: c.green / c.alpha,
blue: c.blue / c.alpha,
alpha: c.alpha,
}
}
fn from(c: Hsla) -> PreMulRgba {
Rgba::from(c).into()
}
fn from(c: PreMulRgba) -> Hsla {
Rgba::from(c).into()
}
fn from(c: Hsva) -> PreMulRgba {
Rgba::from(c).into()
}
fn from(c: PreMulRgba) -> Hsva {
Rgba::from(c).into()
}
}
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Hsla {
pub hue: f32,
pub saturation: f32,
pub lightness: f32,
pub alpha: f32,
}
impl PartialEq for Hsla {
fn eq(&self, other: &Self) -> bool {
about_eq(self.hue, other.hue, EPSILON_100)
&& about_eq(self.saturation, other.saturation, EPSILON)
&& about_eq(self.lightness, other.lightness, EPSILON)
&& about_eq(self.alpha, other.alpha, EPSILON)
}
}
impl std::hash::Hash for Hsla {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
about_eq_hash(self.hue, EPSILON_100, state);
about_eq_hash(self.saturation, EPSILON, state);
about_eq_hash(self.lightness, EPSILON, state);
about_eq_hash(self.alpha, EPSILON, state);
}
}
impl Hsla {
pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
self.hue = hue.into().modulo().0
}
pub fn set_lightness<L: Into<Factor>>(&mut self, lightness: L) {
self.lightness = lightness.into().0;
}
pub fn set_saturation<S: Into<Factor>>(&mut self, saturation: S) {
self.saturation = saturation.into().0;
}
pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
self.alpha = alpha.into().0
}
pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
self.set_hue(hue);
self
}
pub fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
self.set_lightness(lightness);
self
}
pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
self.set_saturation(saturation);
self
}
pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
self.set_alpha(alpha);
self
}
fn lerp_sla(mut self, to: Hsla, factor: Factor) -> Self {
self.saturation = self.saturation.lerp(&to.saturation, factor);
self.lightness = self.lightness.lerp(&to.lightness, factor);
self.alpha = self.alpha.lerp(&to.alpha, factor);
self
}
pub fn slerp(mut self, to: Self, factor: Factor) -> Self {
self = self.lerp_sla(to, factor);
self.hue = AngleDegree(self.hue).slerp(AngleDegree(to.hue), factor).0;
self
}
pub fn is_chromatic(self) -> bool {
self.saturation > 0.0001
}
pub fn slerp_chromatic(mut self, to: Self, factor: Factor) -> Self {
if self.is_chromatic() && to.is_chromatic() {
self.slerp(to, factor)
} else {
self = self.lerp_sla(to, factor);
if to.is_chromatic() {
self.hue = to.hue;
}
self
}
}
fn lerp_hsla(mut self, to: Self, factor: Factor) -> Self {
self = self.lerp_sla(to, factor);
self.hue = self.hue.lerp(&to.hue, factor);
self
}
fn lerp(self, to: Self, factor: Factor) -> Self {
match lerp_space() {
LerpSpace::HslaChromatic => self.slerp_chromatic(to, factor),
LerpSpace::Rgba => lerp_rgba_linear(self.into(), to.into(), factor).into(),
LerpSpace::Hsla => self.slerp(to, factor),
LerpSpace::HslaLinear => self.lerp_hsla(to, factor),
}
}
}
impl fmt::Debug for Hsla {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("Hsla")
.field("hue", &self.hue)
.field("saturation", &self.saturation)
.field("lightness", &self.lightness)
.field("alpha", &self.alpha)
.finish()
} else {
fn p(n: f32) -> f32 {
clamp_normal(n) * 100.0
}
let a = p(self.alpha);
let h = AngleDegree(self.hue).modulo().0.round();
if (a - 100.0).abs() <= EPSILON {
write!(f, "hsl({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.lightness))
} else {
write!(
f,
"hsla({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
p(self.saturation),
p(self.lightness),
a
)
}
}
}
}
impl fmt::Display for Hsla {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn p(n: f32) -> f32 {
clamp_normal(n) * 100.0
}
let a = p(self.alpha);
let h = AngleDegree(self.hue).modulo().0.round();
if (a - 100.0).abs() <= EPSILON {
write!(f, "hsl({h}º, {}%, {}%)", p(self.saturation), p(self.lightness))
} else {
write!(f, "hsla({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.lightness), a)
}
}
}
impl Transitionable for Hsla {
fn lerp(self, to: &Self, step: EasingStep) -> Self {
self.lerp(*to, step)
}
}
#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
pub struct Hsva {
pub hue: f32,
pub saturation: f32,
pub value: f32,
pub alpha: f32,
}
impl PartialEq for Hsva {
fn eq(&self, other: &Self) -> bool {
about_eq(self.hue, other.hue, EPSILON_100)
&& about_eq(self.saturation, other.saturation, EPSILON)
&& about_eq(self.value, other.value, EPSILON)
&& about_eq(self.alpha, other.alpha, EPSILON)
}
}
impl std::hash::Hash for Hsva {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
about_eq_hash(self.hue, EPSILON_100, state);
about_eq_hash(self.saturation, EPSILON, state);
about_eq_hash(self.value, EPSILON, state);
about_eq_hash(self.alpha, EPSILON, state);
}
}
impl Hsva {
pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
self.hue = hue.into().modulo().0
}
pub fn set_value<L: Into<Factor>>(&mut self, value: L) {
self.value = value.into().0;
}
pub fn set_saturation<L: Into<Factor>>(&mut self, saturation: L) {
self.saturation = saturation.into().0;
}
pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
self.alpha = alpha.into().0
}
pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
self.set_hue(hue);
self
}
pub fn with_value<V: Into<Factor>>(mut self, value: V) -> Self {
self.set_value(value);
self
}
pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
self.set_saturation(saturation);
self
}
pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
self.set_alpha(alpha);
self
}
}
impl fmt::Debug for Hsva {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
f.debug_struct("Hsla")
.field("hue", &self.hue)
.field("saturation", &self.saturation)
.field("value", &self.value)
.field("alpha", &self.alpha)
.finish()
} else {
fn p(n: f32) -> f32 {
clamp_normal(n) * 100.0
}
let a = p(self.alpha);
let h = AngleDegree(self.hue).modulo().0.round();
if (a - 100.0).abs() <= EPSILON {
write!(f, "hsv({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.value))
} else {
write!(
f,
"hsva({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
p(self.saturation),
p(self.value),
a
)
}
}
}
}
impl fmt::Display for Hsva {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn p(n: f32) -> f32 {
clamp_normal(n) * 100.0
}
let a = p(self.alpha);
let h = AngleDegree(self.hue).modulo().0.round();
if (a - 100.0).abs() <= EPSILON {
write!(f, "hsv({h}º, {}%, {}%)", p(self.saturation), p(self.value))
} else {
write!(f, "hsva({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.value), a)
}
}
}
impl_from_and_into_var! {
fn from(hsla: Hsla) -> Hsva {
let lightness = clamp_normal(hsla.lightness);
let saturation = clamp_normal(hsla.saturation);
let value = lightness + saturation * lightness.min(1.0 - lightness);
let saturation = if value <= EPSILON {
0.0
} else {
2.0 * (1.0 - lightness / value)
};
Hsva {
hue: hsla.hue,
saturation,
value,
alpha: hsla.alpha,
}
}
fn from(hsva: Hsva) -> Hsla {
let saturation = clamp_normal(hsva.saturation);
let value = clamp_normal(hsva.value);
let lightness = value * (1.0 - saturation / 2.0);
let saturation = if lightness <= EPSILON || lightness >= 1.0 - EPSILON {
0.0
} else {
2.0 * (1.0 * lightness / value)
};
Hsla {
hue: hsva.hue,
saturation,
lightness,
alpha: hsva.alpha,
}
}
fn from(hsva: Hsva) -> Rgba {
let hue = AngleDegree(hsva.hue).modulo().0;
let saturation = clamp_normal(hsva.saturation);
let value = clamp_normal(hsva.value);
let c = value * saturation;
let hue = hue / 60.0;
let x = c * (1.0 - (hue.rem_euclid(2.0) - 1.0).abs());
let (red, green, blue) = if hue <= 1.0 {
(c, x, 0.0)
} else if hue <= 2.0 {
(x, c, 0.0)
} else if hue <= 3.0 {
(0.0, c, x)
} else if hue <= 4.0 {
(0.0, x, c)
} else if hue <= 5.0 {
(x, 0.0, c)
} else if hue <= 6.0 {
(c, 0.0, x)
} else {
(0.0, 0.0, 0.0)
};
let m = value - c;
let f = |n: f32| ((n + m) * 255.0).round() / 255.0;
Rgba {
red: f(red),
green: f(green),
blue: f(blue),
alpha: hsva.alpha,
}
}
fn from(hsla: Hsla) -> Rgba {
if hsla.saturation <= EPSILON {
return rgba(hsla.lightness, hsla.lightness, hsla.lightness, hsla.alpha);
}
let hue = AngleDegree(hsla.hue).modulo().0;
let saturation = clamp_normal(hsla.saturation);
let lightness = clamp_normal(hsla.lightness);
let c = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
let hp = hue / 60.0;
let x = c * (1.0 - ((hp % 2.0) - 1.0).abs());
let (red, green, blue) = if hp <= 1.0 {
(c, x, 0.0)
} else if hp <= 2.0 {
(x, c, 0.0)
} else if hp <= 3.0 {
(0.0, c, x)
} else if hp <= 4.0 {
(0.0, x, c)
} else if hp <= 5.0 {
(x, 0.0, c)
} else if hp <= 6.0 {
(c, 0.0, x)
} else {
(0.0, 0.0, 0.0)
};
let m = lightness - c * 0.5;
let f = |i: f32| ((i + m) * 255.0).round() / 255.0;
Rgba {
red: f(red),
green: f(green),
blue: f(blue),
alpha: hsla.alpha,
}
}
}
impl Transitionable for Hsva {
fn lerp(self, to: &Self, step: EasingStep) -> Self {
match lerp_space() {
LerpSpace::HslaChromatic => Hsla::from(self).slerp_chromatic((*to).into(), step).into(),
LerpSpace::Rgba => lerp_rgba_linear(self.into(), (*to).into(), step).into(),
LerpSpace::Hsla => Hsla::from(self).slerp((*to).into(), step).into(),
LerpSpace::HslaLinear => Hsla::from(self).lerp_hsla((*to).into(), step).into(),
}
}
}
macro_rules! cylindrical_color {
($rgba:ident -> ($min:ident, $max:ident, $delta:ident, $hue:ident)) => {
fn sanitize(i: f32) -> f32 {
clamp_normal((i * 255.0).round() / 255.0)
}
let r = sanitize($rgba.red);
let g = sanitize($rgba.green);
let b = sanitize($rgba.blue);
let $min = r.min(g).min(b);
let $max = r.max(g).max(b);
fn about_eq(a: f32, b: f32) -> bool {
(a - b) <= EPSILON
}
let $delta = $max - $min;
let $hue = if $delta <= EPSILON {
0.0
} else {
60.0 * if about_eq($max, r) {
((g - b) / $delta).rem_euclid(6.0)
} else if about_eq($max, g) {
(b - r) / $delta + 2.0
} else {
debug_assert!(about_eq($max, b));
(r - g) / $delta + 4.0
}
};
};
}
impl_from_and_into_var! {
fn from(rgba: Rgba) -> Hsva {
cylindrical_color!(rgba -> (min, max, delta, hue));
let saturation = if max <= EPSILON { 0.0 } else { delta / max };
let value = max;
Hsva {
hue,
saturation,
value,
alpha: rgba.alpha,
}
}
fn from(rgba: Rgba) -> Hsla {
cylindrical_color!(rgba -> (min, max, delta, hue));
let lightness = (max + min) / 2.0;
let saturation = if delta <= EPSILON {
0.0
} else {
delta / (1.0 - (2.0 * lightness - 1.0).abs())
};
Hsla {
hue,
lightness,
saturation,
alpha: rgba.alpha,
}
}
}
fn clamp_normal(i: f32) -> f32 {
i.clamp(0.0, 1.0)
}
pub fn rgb<C: Into<RgbaComponent>>(red: C, green: C, blue: C) -> Rgba {
rgba(red, green, blue, 1.0)
}
pub fn rgba<C: Into<RgbaComponent>, A: Into<RgbaComponent>>(red: C, green: C, blue: C, alpha: A) -> Rgba {
Rgba {
red: red.into().0,
green: green.into().0,
blue: blue.into().0,
alpha: alpha.into().0,
}
}
pub fn hsl<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, lightness: N) -> Hsla {
hsla(hue, saturation, lightness, 1.0)
}
pub fn hsla<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, lightness: N, alpha: A) -> Hsla {
Hsla {
hue: hue.into().0,
saturation: saturation.into().0,
lightness: lightness.into().0,
alpha: alpha.into().0,
}
}
pub fn hsv<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, value: N) -> Hsva {
hsva(hue, saturation, value, 1.0)
}
pub fn hsva<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, value: N, alpha: A) -> Hsva {
Hsva {
hue: hue.into().0,
saturation: saturation.into().0,
value: value.into().0,
alpha: alpha.into().0,
}
}
context_var! {
pub static COLOR_SCHEME_VAR: ColorScheme = ColorScheme::default();
}
pub fn light_dark(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> LightDark {
LightDark {
light: light.into(),
dark: dark.into(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
pub struct LightDark {
pub dark: Rgba,
pub light: Rgba,
}
impl_from_and_into_var! {
fn from<L: Into<Rgba>, D: Into<Rgba>>((light, dark): (L, D)) -> LightDark {
LightDark {
light: light.into(),
dark: dark.into(),
}
}
fn from(color: Rgba) -> LightDark {
LightDark { dark: color, light: color }
}
fn from(color: Hsva) -> LightDark {
Rgba::from(color).into()
}
fn from(color: Hsla) -> LightDark {
Rgba::from(color).into()
}
fn from(color: LightDark) -> Option<LightDark>;
}
impl IntoVar<Rgba> for LightDark {
type Var = ContextualizedVar<Rgba>;
fn into_var(self) -> Self::Var {
COLOR_SCHEME_VAR.map(move |s| match s {
ColorScheme::Light => self.light,
ColorScheme::Dark => self.dark,
})
}
}
impl LightDark {
pub fn new(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> Self {
Self {
light: light.into(),
dark: dark.into(),
}
}
pub fn shade_fct(mut self, factor: impl Into<Factor>) -> Self {
let mut factor = factor.into();
let (dark_overlay, light_overlay) = if factor > 0.fct() {
(colors::WHITE, colors::BLACK)
} else {
factor = factor.abs();
(colors::BLACK, colors::WHITE)
};
self.dark = dark_overlay.with_alpha(factor).mix_normal(self.dark);
self.light = light_overlay.with_alpha(factor).mix_normal(self.light);
self
}
pub fn shade(self, shade: i8) -> Self {
self.shade_fct(shade as f32 * 0.08)
}
pub fn rgba(self) -> ContextualizedVar<Rgba> {
IntoVar::<Rgba>::into_var(self)
}
pub fn rgba_map<T: VarValue>(self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> impl Var<T> {
COLOR_SCHEME_VAR.map(move |s| match s {
ColorScheme::Light => map(self.light),
ColorScheme::Dark => map(self.dark),
})
}
pub fn rgba_into<T: VarValue + From<Rgba>>(self) -> impl Var<T> {
self.rgba_map(T::from)
}
}
impl ops::Index<ColorScheme> for LightDark {
type Output = Rgba;
fn index(&self, index: ColorScheme) -> &Self::Output {
match index {
ColorScheme::Light => &self.light,
ColorScheme::Dark => &self.dark,
}
}
}
impl ops::IndexMut<ColorScheme> for LightDark {
fn index_mut(&mut self, index: ColorScheme) -> &mut Self::Output {
match index {
ColorScheme::Light => &mut self.light,
ColorScheme::Dark => &mut self.dark,
}
}
}
pub trait LightDarkVarExt {
fn rgba(&self) -> impl Var<Rgba>;
fn rgba_map<T: VarValue>(&self, map: impl FnMut(Rgba) -> T + Send + 'static) -> impl Var<T>;
fn rgba_into<T: VarValue + From<Rgba>>(&self) -> impl Var<T>;
fn map_rgba(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> impl Var<Rgba>;
fn map_rgba_into<T: VarValue + From<Rgba>>(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> impl Var<T>;
fn shade_fct(&self, fct: impl Into<Factor>) -> impl Var<Rgba>;
fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> impl Var<T>;
fn shade(&self, shade: i8) -> impl Var<Rgba>;
fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> impl Var<T>;
}
impl<V: Var<LightDark>> LightDarkVarExt for V {
fn rgba(&self) -> impl Var<Rgba> {
expr_var! {
let c = #{self.clone()};
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => c.light,
ColorScheme::Dark => c.dark,
}
}
}
fn rgba_map<T: VarValue>(&self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> impl Var<T> {
expr_var! {
let c = #{self.clone()};
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => map(c.light),
ColorScheme::Dark => map(c.dark),
}
}
}
fn rgba_into<T: VarValue + From<Rgba>>(&self) -> impl Var<T> {
self.rgba_map(Into::into)
}
fn map_rgba(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> impl Var<Rgba> {
expr_var! {
let c = map(*#{self.clone()});
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => c.light,
ColorScheme::Dark => c.dark,
}
}
}
fn map_rgba_into<T: VarValue + From<Rgba>>(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> impl Var<T> {
expr_var! {
let c = map(*#{self.clone()});
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => T::from(c.light),
ColorScheme::Dark => T::from(c.dark),
}
}
}
fn shade_fct(&self, fct: impl Into<Factor>) -> impl Var<Rgba> {
let fct = fct.into();
expr_var! {
let c = #{self.clone()}.shade_fct(fct);
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => c.light,
ColorScheme::Dark => c.dark,
}
}
}
fn shade(&self, shade: i8) -> impl Var<Rgba> {
expr_var! {
let c = #{self.clone()}.shade(shade);
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => c.light,
ColorScheme::Dark => c.dark,
}
}
}
fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> impl Var<T> {
let fct = fct.into();
expr_var! {
let c = #{self.clone()}.shade_fct(fct);
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => T::from(c.light),
ColorScheme::Dark => T::from(c.dark),
}
}
}
fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> impl Var<T> {
expr_var! {
let c = #{self.clone()}.shade(shade);
match *#{COLOR_SCHEME_VAR} {
ColorScheme::Light => T::from(c.light),
ColorScheme::Dark => T::from(c.dark),
}
}
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum LerpSpace {
Rgba,
Hsla,
#[default]
HslaChromatic,
HslaLinear,
}
pub fn lerp_space() -> LerpSpace {
LERP_SPACE.get_clone()
}
pub fn with_lerp_space<R>(space: LerpSpace, f: impl FnOnce() -> R) -> R {
LERP_SPACE.with_context(&mut Some(Arc::new(space)), f)
}
pub fn rgba_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
with_lerp_space(LerpSpace::Rgba, || t.sample(step))
}
pub fn hsla_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
with_lerp_space(LerpSpace::Hsla, || t.sample(step))
}
pub fn hsla_linear_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
with_lerp_space(LerpSpace::HslaLinear, || t.sample(step))
}
context_local! {
static LERP_SPACE: LerpSpace = LerpSpace::default();
}
#[cfg(test)]
mod tests {
use super::*;
use zng_layout::unit::AngleUnits as _;
#[test]
fn hsl_red() {
assert_eq!(Rgba::from(hsl(0.0.deg(), 100.pct(), 50.pct())), rgb(1.0, 0.0, 0.0))
}
#[test]
fn hsl_color() {
assert_eq!(Rgba::from(hsl(91.0.deg(), 1.0, 0.5)), rgb(123, 255, 0))
}
#[test]
fn rgb_to_hsl() {
let color = rgba(0, 100, 200, 0.2);
let a = format!("{color:?}");
let b = format!("{:?}", Rgba::from(Hsla::from(color)));
assert_eq!(a, b)
}
#[test]
fn rgb_to_hsv() {
let color = rgba(0, 100, 200, 0.2);
let a = format!("{color:?}");
let b = format!("{:?}", Rgba::from(Hsva::from(color)));
assert_eq!(a, b)
}
#[test]
fn rgba_display() {
macro_rules! test {
($($tt:tt)+) => {
let expected = stringify!($($tt)+).replace(" ", "");
let actual = hex!($($tt)+).to_string();
assert_eq!(expected, actual);
}
}
test!(#AABBCC);
test!(#123456);
test!(#000000);
test!(#FFFFFF);
test!(#AABBCCDD);
test!(#12345678);
test!(#00000000);
test!(#FFFFFF00);
}
#[test]
fn test_hex_color() {
fn f(n: u8) -> f32 {
n as f32 / 255.0
}
assert_eq!(Rgba::new(f(0x11), f(0x22), f(0x33), f(0x44)), hex!(0x11223344));
assert_eq!(colors::BLACK, hex!(0x00_00_00_FF));
assert_eq!(colors::WHITE, hex!(0xFF_FF_FF_FF));
assert_eq!(colors::WHITE, hex!(0xFF_FF_FF));
assert_eq!(colors::WHITE, hex!(0xFFFFFF));
assert_eq!(colors::WHITE, hex!(#FFFFFF));
assert_eq!(colors::WHITE, hex!(FFFFFF));
assert_eq!(colors::WHITE, hex!(0xFFFF));
assert_eq!(colors::BLACK, hex!(0x000));
assert_eq!(colors::BLACK, hex!(#000));
assert_eq!(colors::BLACK, hex!(000));
}
}