zng_color/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Color and gradient types, functions and macros, [`Rgba`], [`filter`], [`hex!`] and more.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11#![recursion_limit = "256"]
12
13use std::{fmt, ops, sync::Arc};
14use zng_app_context::context_local;
15
16use zng_layout::unit::{AngleDegree, EQ_GRANULARITY, EQ_GRANULARITY_100, Factor, FactorUnits, about_eq, about_eq_hash, about_eq_ord};
17use zng_var::{
18    IntoVar, Var, VarValue,
19    animation::{Transition, Transitionable, easing::EasingStep},
20    context_var, expr_var, impl_from_and_into_var,
21};
22
23pub use zng_view_api::config::ColorScheme;
24
25#[doc(hidden)]
26pub use zng_color_proc_macros::hex_color;
27
28pub use zng_layout::unit::{Rgba, RgbaComponent};
29
30pub mod colors;
31pub mod filter;
32pub mod gradient;
33pub mod web_colors;
34
35mod mix;
36pub use mix::*;
37
38/// Hexadecimal color literal.
39///
40/// # Syntax
41///
42/// `[#|0x]RRGGBB[AA]` or `[#|0x]RGB[A]`.
43///
44/// An optional prefix `#` or `0x` is supported, after the prefix a hexadecimal integer literal is expected. The literal can be
45/// separated using `_`. No integer type suffix is allowed.
46///
47/// The literal is a sequence of 3 or 4 bytes (red, green, blue and alpha). If the sequence is in pairs each pair is a byte `[00..=FF]`.
48/// If the sequence is in single characters this is a shorthand that repeats the character for each byte, e.g. `#012F` equals `#001122FF`.
49///
50/// # Examples
51///
52/// ```
53/// # use zng_color::hex;
54/// let red = hex!(#FF0000);
55/// let green = hex!(#00FF00);
56/// let blue = hex!(#0000FF);
57/// let red_half_transparent = hex!(#FF00007F);
58///
59/// assert_eq!(red, hex!(#F00));
60/// assert_eq!(red, hex!(0xFF_00_00));
61/// assert_eq!(red, hex!(FF_00_00));
62/// ```
63///
64#[macro_export]
65macro_rules! hex {
66    ($($tt:tt)+) => {
67        $crate::hex_color!{$crate, $($tt)*}
68    };
69}
70
71fn lerp_rgba_linear(mut from: Rgba, to: Rgba, factor: Factor) -> Rgba {
72    from.red = from.red.lerp(&to.red, factor);
73    from.green = from.green.lerp(&to.green, factor);
74    from.blue = from.blue.lerp(&to.blue, factor);
75    from.alpha = from.alpha.lerp(&to.alpha, factor);
76    from
77}
78
79/// Default implementation of lerp for [`Rgba`] in apps.
80///
81/// Implements [`lerp_space`] dependent transition.
82///
83/// Apps set this as the default implementation on init.
84pub fn lerp_rgba(from: Rgba, to: Rgba, factor: Factor) -> Rgba {
85    match lerp_space() {
86        LerpSpace::HslaChromatic => Hsla::from(from).slerp_chromatic(to.into(), factor).into(),
87        LerpSpace::Rgba => lerp_rgba_linear(from, to, factor),
88        LerpSpace::Hsla => Hsla::from(from).slerp(to.into(), factor).into(),
89        LerpSpace::HslaLinear => Hsla::from(from).lerp_hsla(to.into(), factor).into(),
90    }
91}
92
93/// Pre-multiplied RGB + alpha.
94///
95/// Use from/into conversion to create.
96#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
97pub struct PreMulRgba {
98    /// [`Rgba::red`] multiplied by `alpha`.
99    pub red: f32,
100    /// [`Rgba::green`] multiplied by `alpha`.
101    pub green: f32,
102    /// [`Rgba::blue`] multiplied by `alpha`.
103    pub blue: f32,
104    /// Alpha channel value, in the `[0.0..=1.0]` range.
105    pub alpha: f32,
106}
107impl PartialEq for PreMulRgba {
108    fn eq(&self, other: &Self) -> bool {
109        about_eq(self.red, other.red, EQ_GRANULARITY)
110            && about_eq(self.green, other.green, EQ_GRANULARITY)
111            && about_eq(self.blue, other.blue, EQ_GRANULARITY)
112            && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
113    }
114}
115impl Eq for PreMulRgba {}
116impl std::hash::Hash for PreMulRgba {
117    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
118        about_eq_hash(self.red, EQ_GRANULARITY, state);
119        about_eq_hash(self.green, EQ_GRANULARITY, state);
120        about_eq_hash(self.blue, EQ_GRANULARITY, state);
121        about_eq_hash(self.alpha, EQ_GRANULARITY, state);
122    }
123}
124impl PartialOrd for PreMulRgba {
125    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
126        Some(self.cmp(other))
127    }
128}
129impl Ord for PreMulRgba {
130    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
131        about_eq_ord(self.red, other.red, EQ_GRANULARITY)
132            .cmp(&about_eq_ord(self.green, other.green, EQ_GRANULARITY))
133            .cmp(&about_eq_ord(self.blue, other.blue, EQ_GRANULARITY))
134            .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
135    }
136}
137
138impl_from_and_into_var! {
139    fn from(c: Rgba) -> PreMulRgba {
140        PreMulRgba {
141            red: c.red * c.alpha,
142            green: c.green * c.alpha,
143            blue: c.blue * c.alpha,
144            alpha: c.alpha,
145        }
146    }
147
148    fn from(c: PreMulRgba) -> Rgba {
149        Rgba {
150            red: c.red / c.alpha,
151            green: c.green / c.alpha,
152            blue: c.blue / c.alpha,
153            alpha: c.alpha,
154        }
155    }
156
157    fn from(c: Hsla) -> PreMulRgba {
158        Rgba::from(c).into()
159    }
160
161    fn from(c: PreMulRgba) -> Hsla {
162        Rgba::from(c).into()
163    }
164
165    fn from(c: Hsva) -> PreMulRgba {
166        Rgba::from(c).into()
167    }
168
169    fn from(c: PreMulRgba) -> Hsva {
170        Rgba::from(c).into()
171    }
172}
173
174/// HSL + alpha.
175///
176/// # Equality
177///
178/// Equality is determined using [`about_eq`] with `0.001` granularity for [`hue`](Hsla::hue)
179/// and `0.00001` granularity for the others.
180///
181/// [`about_eq`]: zng_layout::unit::about_eq
182#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
183pub struct Hsla {
184    /// Hue color angle in the `[0.0..=360.0]` range.
185    pub hue: f32,
186    /// Saturation amount in the `[0.0..=1.0]` range, zero is gray, one is full color.
187    pub saturation: f32,
188    /// Lightness amount in the `[0.0..=1.0]` range, zero is black, one is white.
189    pub lightness: f32,
190    /// Alpha channel in the `[0.0..=1.0]` range, zero is invisible, one is opaque.
191    pub alpha: f32,
192}
193impl PartialEq for Hsla {
194    fn eq(&self, other: &Self) -> bool {
195        about_eq(self.hue, other.hue, EQ_GRANULARITY_100)
196            && about_eq(self.saturation, other.saturation, EQ_GRANULARITY)
197            && about_eq(self.lightness, other.lightness, EQ_GRANULARITY)
198            && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
199    }
200}
201impl Eq for Hsla {}
202impl PartialOrd for Hsla {
203    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
204        Some(self.cmp(other))
205    }
206}
207impl Ord for Hsla {
208    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
209        about_eq_ord(self.hue, other.hue, EQ_GRANULARITY_100)
210            .cmp(&about_eq_ord(self.saturation, other.saturation, EQ_GRANULARITY))
211            .cmp(&about_eq_ord(self.lightness, other.lightness, EQ_GRANULARITY))
212            .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
213    }
214}
215impl std::hash::Hash for Hsla {
216    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
217        about_eq_hash(self.hue, EQ_GRANULARITY_100, state);
218        about_eq_hash(self.saturation, EQ_GRANULARITY, state);
219        about_eq_hash(self.lightness, EQ_GRANULARITY, state);
220        about_eq_hash(self.alpha, EQ_GRANULARITY, state);
221    }
222}
223impl Hsla {
224    /// Sets the [`hue`](Self::hue) color angle.
225    ///
226    /// The value is normalized to be in the `[0.0..=360.0]` range, that is `362.deg()` becomes `2.0`.
227    pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
228        self.hue = hue.into().modulo().0
229    }
230
231    /// Sets the [`lightness`](Self::lightness) value.
232    pub fn set_lightness<L: Into<Factor>>(&mut self, lightness: L) {
233        self.lightness = lightness.into().0;
234    }
235
236    /// Sets the [`saturation`](Self::saturation) value.
237    pub fn set_saturation<S: Into<Factor>>(&mut self, saturation: S) {
238        self.saturation = saturation.into().0;
239    }
240
241    /// Sets the [`alpha`](Self::alpha) value.
242    pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
243        self.alpha = alpha.into().0
244    }
245
246    /// Returns a copy of this color with a new `hue`.
247    pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
248        self.set_hue(hue);
249        self
250    }
251
252    /// Returns a copy of this color with a new `lightness`.
253    pub fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
254        self.set_lightness(lightness);
255        self
256    }
257
258    /// Returns a copy of this color with a new `saturation`.
259    pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
260        self.set_saturation(saturation);
261        self
262    }
263
264    /// Returns a copy of this color with a new `alpha`.
265    pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
266        self.set_alpha(alpha);
267        self
268    }
269
270    fn lerp_sla(mut self, to: Hsla, factor: Factor) -> Self {
271        self.saturation = self.saturation.lerp(&to.saturation, factor);
272        self.lightness = self.lightness.lerp(&to.lightness, factor);
273        self.alpha = self.alpha.lerp(&to.alpha, factor);
274        self
275    }
276
277    /// Interpolate in the [`LerpSpace::Hsla`] mode.
278    pub fn slerp(mut self, to: Self, factor: Factor) -> Self {
279        self = self.lerp_sla(to, factor);
280        self.hue = AngleDegree(self.hue).slerp(AngleDegree(to.hue), factor).0;
281        self
282    }
283
284    /// If the saturation is more than zero.
285    ///
286    /// If `false` the color is achromatic, the hue value does not affect the color.
287    pub fn is_chromatic(self) -> bool {
288        self.saturation > 0.0001
289    }
290
291    /// Interpolate in the [`LerpSpace::HslaChromatic`] mode.
292    pub fn slerp_chromatic(mut self, to: Self, factor: Factor) -> Self {
293        if self.is_chromatic() && to.is_chromatic() {
294            self.slerp(to, factor)
295        } else {
296            self = self.lerp_sla(to, factor);
297            if to.is_chromatic() {
298                self.hue = to.hue;
299            }
300            self
301        }
302    }
303
304    fn lerp_hsla(mut self, to: Self, factor: Factor) -> Self {
305        self = self.lerp_sla(to, factor);
306        self.hue = self.hue.lerp(&to.hue, factor);
307        self
308    }
309
310    fn lerp(self, to: Self, factor: Factor) -> Self {
311        match lerp_space() {
312            LerpSpace::HslaChromatic => self.slerp_chromatic(to, factor),
313            LerpSpace::Rgba => lerp_rgba_linear(self.into(), to.into(), factor).into(),
314            LerpSpace::Hsla => self.slerp(to, factor),
315            LerpSpace::HslaLinear => self.lerp_hsla(to, factor),
316        }
317    }
318
319    /// If all components are finite.
320    fn is_valid(&self) -> bool {
321        self.hue.is_finite() && self.alpha.is_finite() && self.lightness.is_finite() && self.saturation.is_finite()
322    }
323}
324impl fmt::Debug for Hsla {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        if f.alternate() || !self.is_valid() {
327            f.debug_struct("Hsla")
328                .field("hue", &self.hue)
329                .field("saturation", &self.saturation)
330                .field("lightness", &self.lightness)
331                .field("alpha", &self.alpha)
332                .finish()
333        } else {
334            fn p(n: f32) -> f32 {
335                clamp_normal(n) * 100.0
336            }
337            let a = p(self.alpha);
338            let h = AngleDegree(self.hue).modulo().0.round();
339            if (a - 100.0).abs() <= EQ_GRANULARITY {
340                write!(f, "hsl({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.lightness))
341            } else {
342                write!(
343                    f,
344                    "hsla({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
345                    p(self.saturation),
346                    p(self.lightness),
347                    a
348                )
349            }
350        }
351    }
352}
353impl fmt::Display for Hsla {
354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355        fn p(n: f32) -> f32 {
356            clamp_normal(n) * 100.0
357        }
358        let a = p(self.alpha);
359        let h = AngleDegree(self.hue).modulo().0.round();
360        if (a - 100.0).abs() <= EQ_GRANULARITY {
361            write!(f, "hsl({h}º, {}%, {}%)", p(self.saturation), p(self.lightness))
362        } else {
363            write!(f, "hsla({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.lightness), a)
364        }
365    }
366}
367impl Transitionable for Hsla {
368    fn lerp(self, to: &Self, step: EasingStep) -> Self {
369        self.lerp(*to, step)
370    }
371}
372
373/// HSV + alpha
374///
375/// # Equality
376///
377/// Equality is determined using [`about_eq`] with `0.001` granularity for [`hue`](Hsva::hue)
378/// and `0.00001` granularity for the others.
379///
380/// [`about_eq`]: zng_layout::unit::about_eq
381#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
382pub struct Hsva {
383    /// Hue color angle in the `[0.0..=360.0]` range.
384    pub hue: f32,
385    /// Saturation amount in the `[0.0..=1.0]` range, zero is gray, one is full color.
386    pub saturation: f32,
387    /// Brightness amount in the `[0.0..=1.0]` range, zero is black, one is white.
388    pub value: f32,
389    /// Alpha channel in the `[0.0..=1.0]` range, zero is invisible, one is opaque.
390    pub alpha: f32,
391}
392impl PartialEq for Hsva {
393    fn eq(&self, other: &Self) -> bool {
394        about_eq(self.hue, other.hue, EQ_GRANULARITY_100)
395            && about_eq(self.saturation, other.saturation, EQ_GRANULARITY)
396            && about_eq(self.value, other.value, EQ_GRANULARITY)
397            && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
398    }
399}
400impl Eq for Hsva {}
401impl PartialOrd for Hsva {
402    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
403        Some(self.cmp(other))
404    }
405}
406impl Ord for Hsva {
407    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
408        about_eq_ord(self.hue, other.hue, EQ_GRANULARITY_100)
409            .cmp(&about_eq_ord(self.saturation, other.saturation, EQ_GRANULARITY))
410            .cmp(&about_eq_ord(self.value, other.value, EQ_GRANULARITY))
411            .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
412    }
413}
414impl std::hash::Hash for Hsva {
415    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
416        about_eq_hash(self.hue, EQ_GRANULARITY_100, state);
417        about_eq_hash(self.saturation, EQ_GRANULARITY, state);
418        about_eq_hash(self.value, EQ_GRANULARITY, state);
419        about_eq_hash(self.alpha, EQ_GRANULARITY, state);
420    }
421}
422impl Hsva {
423    /// Sets the [`hue`](Self::hue) color angle.
424    ///
425    /// The value is normalized to be in the `[0.0..=360.0]` range, that is `362.deg()` becomes `2.0`.
426    pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
427        self.hue = hue.into().modulo().0
428    }
429
430    /// Sets the [`value`](Self::value).
431    pub fn set_value<L: Into<Factor>>(&mut self, value: L) {
432        self.value = value.into().0;
433    }
434
435    /// Sets the [`saturation`](Self::saturation) value.
436    pub fn set_saturation<L: Into<Factor>>(&mut self, saturation: L) {
437        self.saturation = saturation.into().0;
438    }
439
440    /// Sets the [`alpha`](Self::alpha) value.
441    pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
442        self.alpha = alpha.into().0
443    }
444
445    /// Returns a copy of this color with a new `hue`.
446    pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
447        self.set_hue(hue);
448        self
449    }
450
451    /// Returns a copy of this color with a new `value`.
452    pub fn with_value<V: Into<Factor>>(mut self, value: V) -> Self {
453        self.set_value(value);
454        self
455    }
456
457    /// Returns a copy of this color with a new `saturation`.
458    pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
459        self.set_saturation(saturation);
460        self
461    }
462
463    /// Returns a copy of this color with a new `alpha`.
464    pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
465        self.set_alpha(alpha);
466        self
467    }
468
469    /// If all components are finite.
470    pub fn is_valid(&self) -> bool {
471        self.hue.is_finite() && self.alpha.is_finite() && self.value.is_finite() && self.saturation.is_finite()
472    }
473}
474impl fmt::Debug for Hsva {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476        if f.alternate() || !self.is_valid() {
477            f.debug_struct("Hsla")
478                .field("hue", &self.hue)
479                .field("saturation", &self.saturation)
480                .field("value", &self.value)
481                .field("alpha", &self.alpha)
482                .finish()
483        } else {
484            fn p(n: f32) -> f32 {
485                clamp_normal(n) * 100.0
486            }
487            let a = p(self.alpha);
488            let h = AngleDegree(self.hue).modulo().0.round();
489            if (a - 100.0).abs() <= EQ_GRANULARITY {
490                write!(f, "hsv({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.value))
491            } else {
492                write!(
493                    f,
494                    "hsva({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
495                    p(self.saturation),
496                    p(self.value),
497                    a
498                )
499            }
500        }
501    }
502}
503impl fmt::Display for Hsva {
504    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505        fn p(n: f32) -> f32 {
506            clamp_normal(n) * 100.0
507        }
508        let a = p(self.alpha);
509        let h = AngleDegree(self.hue).modulo().0.round();
510        if (a - 100.0).abs() <= EQ_GRANULARITY {
511            write!(f, "hsv({h}º, {}%, {}%)", p(self.saturation), p(self.value))
512        } else {
513            write!(f, "hsva({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.value), a)
514        }
515    }
516}
517impl_from_and_into_var! {
518    fn from(hsla: Hsla) -> Hsva {
519        let lightness = clamp_normal(hsla.lightness);
520        let saturation = clamp_normal(hsla.saturation);
521
522        let value = lightness + saturation * lightness.min(1.0 - lightness);
523        let saturation = if value <= EQ_GRANULARITY {
524            0.0
525        } else {
526            2.0 * (1.0 - lightness / value)
527        };
528
529        Hsva {
530            hue: hsla.hue,
531            saturation,
532            value,
533            alpha: hsla.alpha,
534        }
535    }
536
537    fn from(hsva: Hsva) -> Hsla {
538        let saturation = clamp_normal(hsva.saturation);
539        let value = clamp_normal(hsva.value);
540
541        let lightness = value * (1.0 - saturation / 2.0);
542        let saturation = if lightness <= EQ_GRANULARITY || lightness >= 1.0 - EQ_GRANULARITY {
543            0.0
544        } else {
545            2.0 * (1.0 * lightness / value)
546        };
547
548        Hsla {
549            hue: hsva.hue,
550            saturation,
551            lightness,
552            alpha: hsva.alpha,
553        }
554    }
555
556    fn from(hsva: Hsva) -> Rgba {
557        let hue = AngleDegree(hsva.hue).modulo().0;
558        let saturation = clamp_normal(hsva.saturation);
559        let value = clamp_normal(hsva.value);
560
561        let c = value * saturation;
562        let hue = hue / 60.0;
563        let x = c * (1.0 - (hue.rem_euclid(2.0) - 1.0).abs());
564
565        let (red, green, blue) = if hue <= 1.0 {
566            (c, x, 0.0)
567        } else if hue <= 2.0 {
568            (x, c, 0.0)
569        } else if hue <= 3.0 {
570            (0.0, c, x)
571        } else if hue <= 4.0 {
572            (0.0, x, c)
573        } else if hue <= 5.0 {
574            (x, 0.0, c)
575        } else if hue <= 6.0 {
576            (c, 0.0, x)
577        } else {
578            (0.0, 0.0, 0.0)
579        };
580
581        let m = value - c;
582
583        let f = |n: f32| ((n + m) * 255.0).round() / 255.0;
584
585        Rgba {
586            red: f(red),
587            green: f(green),
588            blue: f(blue),
589            alpha: hsva.alpha,
590        }
591    }
592
593    fn from(hsla: Hsla) -> Rgba {
594        if hsla.saturation <= EQ_GRANULARITY {
595            return rgba(hsla.lightness, hsla.lightness, hsla.lightness, hsla.alpha);
596        }
597
598        let hue = AngleDegree(hsla.hue).modulo().0;
599        let saturation = clamp_normal(hsla.saturation);
600        let lightness = clamp_normal(hsla.lightness);
601
602        let c = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
603        let hp = hue / 60.0;
604        let x = c * (1.0 - ((hp % 2.0) - 1.0).abs());
605        let (red, green, blue) = if hp <= 1.0 {
606            (c, x, 0.0)
607        } else if hp <= 2.0 {
608            (x, c, 0.0)
609        } else if hp <= 3.0 {
610            (0.0, c, x)
611        } else if hp <= 4.0 {
612            (0.0, x, c)
613        } else if hp <= 5.0 {
614            (x, 0.0, c)
615        } else if hp <= 6.0 {
616            (c, 0.0, x)
617        } else {
618            (0.0, 0.0, 0.0)
619        };
620        let m = lightness - c * 0.5;
621
622        let f = |i: f32| ((i + m) * 255.0).round() / 255.0;
623
624        Rgba {
625            red: f(red),
626            green: f(green),
627            blue: f(blue),
628            alpha: hsla.alpha,
629        }
630    }
631}
632impl Transitionable for Hsva {
633    fn lerp(self, to: &Self, step: EasingStep) -> Self {
634        match lerp_space() {
635            LerpSpace::HslaChromatic => Hsla::from(self).slerp_chromatic((*to).into(), step).into(),
636            LerpSpace::Rgba => lerp_rgba_linear(self.into(), (*to).into(), step).into(),
637            LerpSpace::Hsla => Hsla::from(self).slerp((*to).into(), step).into(),
638            LerpSpace::HslaLinear => Hsla::from(self).lerp_hsla((*to).into(), step).into(),
639        }
640    }
641}
642
643macro_rules! cylindrical_color {
644    ($rgba:ident -> ($min:ident, $max:ident, $delta:ident, $hue:ident)) => {
645        fn sanitize(i: f32) -> f32 {
646            clamp_normal((i * 255.0).round() / 255.0)
647        }
648
649        let r = sanitize($rgba.red);
650        let g = sanitize($rgba.green);
651        let b = sanitize($rgba.blue);
652
653        let $min = r.min(g).min(b);
654        let $max = r.max(g).max(b);
655
656        fn about_eq(a: f32, b: f32) -> bool {
657            (a - b) <= EQ_GRANULARITY
658        }
659
660        let $delta = $max - $min;
661
662        let $hue = if $delta <= EQ_GRANULARITY {
663            0.0
664        } else {
665            60.0 * if about_eq($max, r) {
666                ((g - b) / $delta).rem_euclid(6.0)
667            } else if about_eq($max, g) {
668                (b - r) / $delta + 2.0
669            } else {
670                debug_assert!(about_eq($max, b));
671                (r - g) / $delta + 4.0
672            }
673        };
674    };
675}
676
677impl_from_and_into_var! {
678    fn from(rgba: Rgba) -> Hsva {
679        cylindrical_color!(rgba -> (min, max, delta, hue));
680
681        let saturation = if max <= EQ_GRANULARITY { 0.0 } else { delta / max };
682
683        let value = max;
684
685        Hsva {
686            hue,
687            saturation,
688            value,
689            alpha: rgba.alpha,
690        }
691    }
692
693    fn from(rgba: Rgba) -> Hsla {
694        cylindrical_color!(rgba -> (min, max, delta, hue));
695
696        let lightness = (max + min) / 2.0;
697
698        let saturation = if delta <= EQ_GRANULARITY {
699            0.0
700        } else {
701            delta / (1.0 - (2.0 * lightness - 1.0).abs())
702        };
703
704        Hsla {
705            hue,
706            lightness,
707            saturation,
708            alpha: rgba.alpha,
709        }
710    }
711}
712
713// Util
714fn clamp_normal(i: f32) -> f32 {
715    i.clamp(0.0, 1.0)
716}
717
718/// RGB color, opaque, alpha is set to `1.0`.
719///
720/// # Arguments
721///
722/// The arguments can either be [`f32`] in the `0.0..=1.0` range or
723/// [`u8`] in the `0..=255` range or a [percentage](zng_layout::unit::FactorPercent).
724///
725/// # Examples
726///
727/// ```
728/// use zng_color::rgb;
729///
730/// let red = rgb(1.0, 0.0, 0.0);
731/// let green = rgb(0, 255, 0);
732/// ```
733pub fn rgb<C: Into<RgbaComponent>>(red: C, green: C, blue: C) -> Rgba {
734    rgba(red, green, blue, 1.0)
735}
736
737/// RGBA color.
738///
739/// # Arguments
740///
741/// The arguments can either be `f32` in the `0.0..=1.0` range or
742/// `u8` in the `0..=255` range or a [percentage](zng_layout::unit::FactorPercent).
743///
744/// The rgb arguments must be of the same type, the alpha argument can be of a different type.
745///
746/// # Examples
747///
748/// ```
749/// use zng_color::rgba;
750///
751/// let half_red = rgba(255, 0, 0, 0.5);
752/// let green = rgba(0.0, 1.0, 0.0, 1.0);
753/// let transparent = rgba(0, 0, 0, 0);
754/// ```
755pub fn rgba<C: Into<RgbaComponent>, A: Into<RgbaComponent>>(red: C, green: C, blue: C, alpha: A) -> Rgba {
756    Rgba {
757        red: red.into().0,
758        green: green.into().0,
759        blue: blue.into().0,
760        alpha: alpha.into().0,
761    }
762}
763
764/// HSL color, opaque, alpha is set to `1.0`.
765///
766/// # Arguments
767///
768/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
769/// range or a [percentage](zng_layout::unit::FactorPercent).
770///
771/// The `saturation` and `lightness` arguments must be of the same type.
772///
773/// # Examples
774///
775/// ```
776/// use zng_color::hsl;
777/// use zng_layout::unit::*;
778///
779/// let red = hsl(0.deg(), 100.pct(), 50.pct());
780/// let green = hsl(115.deg(), 1.0, 0.5);
781/// ```
782///
783/// [angle unit]: trait@zng_layout::unit::AngleUnits
784pub fn hsl<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, lightness: N) -> Hsla {
785    hsla(hue, saturation, lightness, 1.0)
786}
787
788/// HSLA color.
789///
790/// # Arguments
791///
792/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
793/// range or a [percentage](zng_layout::unit::FactorPercent).
794///
795/// The `saturation` and `lightness` arguments must be of the same type.
796///
797/// # Examples
798///
799/// ```
800/// use zng_color::hsla;
801/// use zng_layout::unit::*;
802///
803/// let red = hsla(0.deg(), 100.pct(), 50.pct(), 1.0);
804/// let green = hsla(115.deg(), 1.0, 0.5, 100.pct());
805/// let transparent = hsla(0.deg(), 1.0, 0.5, 0.0);
806/// ```
807///
808/// [angle unit]: trait@zng_layout::unit::AngleUnits
809pub fn hsla<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, lightness: N, alpha: A) -> Hsla {
810    let c = Hsla {
811        hue: hue.into().0,
812        saturation: saturation.into().0,
813        lightness: lightness.into().0,
814        alpha: alpha.into().0,
815    };
816    debug_assert!(c.is_valid(), "hsla color components must be finite, was {c:?}");
817    c
818}
819
820/// HSV color, opaque, alpha is set to `1.0`.
821///
822/// # Arguments
823///
824/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
825/// range or a [percentage](zng_layout::unit::FactorPercent).
826///
827/// The `saturation` and `value` arguments must be of the same type.
828///
829/// # Examples
830///
831/// ```
832/// use zng_color::hsv;
833/// use zng_layout::unit::*;
834///
835/// let red = hsv(0.deg(), 100.pct(), 50.pct());
836/// let green = hsv(115.deg(), 1.0, 0.5);
837/// ```
838///
839/// [angle unit]: trait@zng_layout::unit::AngleUnits
840pub fn hsv<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, value: N) -> Hsva {
841    hsva(hue, saturation, value, 1.0)
842}
843
844/// HSVA color.
845///
846/// # Arguments
847///
848/// The first argument `hue` can be any [angle unit]. The other two arguments can be [`f32`] in the `0.0..=1.0`
849/// range or a [percentage](zng_layout::unit::FactorPercent).
850///
851/// The `saturation` and `value` arguments must be of the same type.
852///
853/// # Examples
854///
855/// ```
856/// use zng_color::hsva;
857/// use zng_layout::unit::*;
858///
859/// let red = hsva(0.deg(), 100.pct(), 50.pct(), 1.0);
860/// let green = hsva(115.deg(), 1.0, 0.5, 100.pct());
861/// let transparent = hsva(0.deg(), 1.0, 0.5, 0.0);
862/// ```
863///
864/// [angle unit]: trait@zng_layout::unit::AngleUnits
865pub fn hsva<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, value: N, alpha: A) -> Hsva {
866    let c = Hsva {
867        hue: hue.into().0,
868        saturation: saturation.into().0,
869        value: value.into().0,
870        alpha: alpha.into().0,
871    };
872    debug_assert!(c.is_valid(), "hsva color components must be finite, was {c:?}");
873    c
874}
875
876context_var! {
877    /// Defines the preferred color scheme in a context.
878    pub static COLOR_SCHEME_VAR: ColorScheme = ColorScheme::default();
879}
880
881/// RGBA color pair.
882///
883/// # Arguments
884///
885/// The arguments can be any color type that converts to [`Rgba`]. The first color
886/// is used in [`ColorScheme::Dark`] contexts, the second color is used in [`ColorScheme::Light`] contexts.
887///
888/// Note that [`LightDark`] converts `IntoVar<Rgba>` with a contextual var that selects the color, so you
889/// can just set color properties directly with a color pair .
890pub fn light_dark(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> LightDark {
891    LightDark {
892        light: light.into(),
893        dark: dark.into(),
894    }
895}
896
897/// Represents a dark and light *color*.
898///
899///
900/// Note that this struct converts `IntoVar<Rgba>` with a contextual var that selects the color, so you
901/// can just set color properties directly with a color pair.
902#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
903pub struct LightDark {
904    /// Color used when [`ColorScheme::Dark`].
905    pub dark: Rgba,
906    /// Color used when [`ColorScheme::Light`].
907    pub light: Rgba,
908}
909impl_from_and_into_var! {
910    /// From `(light, dark)` tuple.
911    fn from<L: Into<Rgba>, D: Into<Rgba>>((light, dark): (L, D)) -> LightDark {
912        LightDark {
913            light: light.into(),
914            dark: dark.into(),
915        }
916    }
917
918    /// From same color to both.
919    fn from(color: Rgba) -> LightDark {
920        LightDark { dark: color, light: color }
921    }
922
923    /// From same color to both.
924    fn from(color: Hsva) -> LightDark {
925        Rgba::from(color).into()
926    }
927
928    /// From same color to both.
929    fn from(color: Hsla) -> LightDark {
930        Rgba::from(color).into()
931    }
932
933    fn from(color: LightDark) -> Option<LightDark>;
934}
935impl IntoVar<Rgba> for LightDark {
936    fn into_var(self) -> Var<Rgba> {
937        COLOR_SCHEME_VAR.map(move |s| match s {
938            ColorScheme::Light => self.light,
939            ColorScheme::Dark => self.dark,
940            _ => self.light,
941        })
942    }
943}
944impl LightDark {
945    /// New from light, dark colors.
946    pub fn new(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> Self {
947        Self {
948            light: light.into(),
949            dark: dark.into(),
950        }
951    }
952
953    /// Overlay WHITE/BLACK to the dark/light color depending on the `factor`, negative factor inverts overlay.
954    pub fn shade_fct(mut self, factor: impl Into<Factor>) -> Self {
955        let mut factor = factor.into();
956        let (dark_overlay, light_overlay) = if factor > 0.fct() {
957            (colors::WHITE, colors::BLACK)
958        } else {
959            factor = factor.abs();
960            (colors::BLACK, colors::WHITE)
961        };
962        self.dark = dark_overlay.with_alpha(factor).mix_normal(self.dark);
963        self.light = light_overlay.with_alpha(factor).mix_normal(self.light);
964        self
965    }
966
967    /// Shade at 8% increments.
968    ///
969    /// Common usage: 1=hovered, 2=pressed.
970    pub fn shade(self, shade: i8) -> Self {
971        self.shade_fct(shade as f32 * 0.08)
972    }
973
974    /// Gets a contextual `Rgba` var that selects the color for the context scheme.
975    ///
976    /// Also see [`LightDarkVarExt`] for mapping from vars.
977    pub fn rgba(self) -> Var<Rgba> {
978        IntoVar::<Rgba>::into_var(self)
979    }
980
981    /// Gets a contextual `Rgba` var that selects the color for the context scheme and `map` it.
982    pub fn rgba_map<T: VarValue>(self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T> {
983        COLOR_SCHEME_VAR.map(move |s| match s {
984            ColorScheme::Light => map(self.light),
985            ColorScheme::Dark => map(self.dark),
986            _ => map(self.light),
987        })
988    }
989
990    /// Gets a contextual `Rgba` var that selects the color for the context scheme and converts it to `T`.
991    pub fn rgba_into<T: VarValue + From<Rgba>>(self) -> Var<T> {
992        self.rgba_map(T::from)
993    }
994}
995impl ops::Index<ColorScheme> for LightDark {
996    type Output = Rgba;
997
998    fn index(&self, index: ColorScheme) -> &Self::Output {
999        match index {
1000            ColorScheme::Light => &self.light,
1001            ColorScheme::Dark => &self.dark,
1002            _ => &self.light,
1003        }
1004    }
1005}
1006impl ops::IndexMut<ColorScheme> for LightDark {
1007    fn index_mut(&mut self, index: ColorScheme) -> &mut Self::Output {
1008        match index {
1009            ColorScheme::Light => &mut self.light,
1010            ColorScheme::Dark => &mut self.dark,
1011            _ => &mut self.light,
1012        }
1013    }
1014}
1015
1016/// Extension methods for `Var<LightDark>`.
1017pub trait LightDarkVarExt {
1018    /// Gets a contextualized var that maps to [`LightDark::rgba`].
1019    fn rgba(&self) -> Var<Rgba>;
1020    /// Gets a contextualized var that maps to [`LightDark::rgba`] and `map`.
1021    fn rgba_map<T: VarValue>(&self, map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T>;
1022    /// Gets a contextualized var that maps to [`LightDark::rgba`] converted into `T`.
1023    fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T>;
1024
1025    /// Gets a contextualized var that maps using `map` and then to [`LightDark::rgba`].
1026    fn map_rgba(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba>;
1027    /// Gets a contextualized var that maps using `map` and then into `T`.
1028    fn map_rgba_into<T: VarValue + From<Rgba>>(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T>;
1029
1030    /// Gets a contextualized var that maps to [`LightDark::shade_fct`] and then to [`LightDark::rgba`].
1031    fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba>;
1032    /// Gets a contextualized var that maps to [`LightDark::shade_fct`] and then to [`LightDark::rgba`] and then into `T`.
1033    fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T>;
1034
1035    /// Gets a contextualized var that maps to [`LightDark::shade`] and then to [`LightDark::rgba`].   
1036    ///
1037    /// * +1 - Hovered.
1038    /// * +2 - Pressed.
1039    fn shade(&self, shade: i8) -> Var<Rgba>;
1040    /// Gets a contextualized var that maps to [`LightDark::shade`] and then to [`LightDark::rgba`] and then into `T`.
1041    fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> Var<T>;
1042}
1043impl LightDarkVarExt for Var<LightDark> {
1044    fn rgba(&self) -> Var<Rgba> {
1045        expr_var! {
1046            let c = #{self.clone()};
1047            match *#{COLOR_SCHEME_VAR} {
1048                ColorScheme::Light => c.light,
1049                ColorScheme::Dark => c.dark,
1050                _ => c.light,
1051            }
1052        }
1053    }
1054
1055    fn rgba_map<T: VarValue>(&self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T> {
1056        expr_var! {
1057            let c = #{self.clone()};
1058            match *#{COLOR_SCHEME_VAR} {
1059                ColorScheme::Light => map(c.light),
1060                ColorScheme::Dark => map(c.dark),
1061                _ => map(c.light),
1062            }
1063        }
1064    }
1065
1066    fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T> {
1067        self.rgba_map(Into::into)
1068    }
1069
1070    fn map_rgba(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba> {
1071        expr_var! {
1072            let c = map(*#{self.clone()});
1073            match *#{COLOR_SCHEME_VAR} {
1074                ColorScheme::Light => c.light,
1075                ColorScheme::Dark => c.dark,
1076                _ => c.light,
1077            }
1078        }
1079    }
1080
1081    fn map_rgba_into<T: VarValue + From<Rgba>>(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T> {
1082        expr_var! {
1083            let c = map(*#{self.clone()});
1084            match *#{COLOR_SCHEME_VAR} {
1085                ColorScheme::Light => T::from(c.light),
1086                ColorScheme::Dark => T::from(c.dark),
1087                _ => T::from(c.light),
1088            }
1089        }
1090    }
1091
1092    fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba> {
1093        let fct = fct.into();
1094        expr_var! {
1095            let c = #{self.clone()}.shade_fct(fct);
1096            match *#{COLOR_SCHEME_VAR} {
1097                ColorScheme::Light => c.light,
1098                ColorScheme::Dark => c.dark,
1099                _ => c.light,
1100            }
1101        }
1102    }
1103
1104    fn shade(&self, shade: i8) -> Var<Rgba> {
1105        expr_var! {
1106            let c = #{self.clone()}.shade(shade);
1107            match *#{COLOR_SCHEME_VAR} {
1108                ColorScheme::Light => c.light,
1109                ColorScheme::Dark => c.dark,
1110                _ => c.light,
1111            }
1112        }
1113    }
1114
1115    fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T> {
1116        let fct = fct.into();
1117        expr_var! {
1118            let c = #{self.clone()}.shade_fct(fct);
1119            match *#{COLOR_SCHEME_VAR} {
1120                ColorScheme::Light => T::from(c.light),
1121                ColorScheme::Dark => T::from(c.dark),
1122                _ => T::from(c.light),
1123            }
1124        }
1125    }
1126
1127    fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> Var<T> {
1128        expr_var! {
1129            let c = #{self.clone()}.shade(shade);
1130            match *#{COLOR_SCHEME_VAR} {
1131                ColorScheme::Light => T::from(c.light),
1132                ColorScheme::Dark => T::from(c.dark),
1133                _ => T::from(c.light),
1134            }
1135        }
1136    }
1137}
1138
1139/// Defines the color space for color interpolation.
1140///
1141/// See [`with_lerp_space`] for more details.
1142#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1143pub enum LerpSpace {
1144    /// Linear interpolation in each RGBA component.
1145    Rgba,
1146    /// Spherical linear interpolation in Hue (shorter path), linear interpolation in SLA.
1147    Hsla,
1148    /// Linear interpolate SLA, spherical linear interpolation in Hue (short) if both colors are chromatic (S>0) or
1149    /// jumps to the chromatic hue from the start.
1150    #[default]
1151    HslaChromatic,
1152    /// Linear interpolation in each HSLA component.
1153    HslaLinear,
1154}
1155
1156/// Gets the lerp space used for color interpolation.
1157///
1158/// Must be called only inside the [`with_lerp_space`] closure or in the lerp implementation of a variable animating
1159/// with [`rgba_sampler`] or [`hsla_linear_sampler`].
1160pub fn lerp_space() -> LerpSpace {
1161    LERP_SPACE.get_clone()
1162}
1163
1164/// Calls `f` with [`lerp_space`] set to `space`.
1165///
1166/// See [`rgba_sampler`] and [`hsla_linear_sampler`] for a way to set the space in animations.
1167pub fn with_lerp_space<R>(space: LerpSpace, f: impl FnOnce() -> R) -> R {
1168    LERP_SPACE.with_context(&mut Some(Arc::new(space)), f)
1169}
1170
1171/// Animation sampler that sets the [`lerp_space`] to [`LerpSpace::Rgba`].
1172///
1173/// Samplers can be set in animations using the [`Var::easing_with`] method.
1174///
1175/// [`Var::easing_with`]: zng_var::Var::easing_with
1176pub fn rgba_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1177    with_lerp_space(LerpSpace::Rgba, || t.sample(step))
1178}
1179
1180/// Animation sampler that sets the [`lerp_space`] to [`LerpSpace::Hsla`].
1181///
1182/// Note that this is already the default.
1183pub fn hsla_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1184    with_lerp_space(LerpSpace::Hsla, || t.sample(step))
1185}
1186
1187/// Animation sampler that sets the [`lerp_space`] to [`LerpSpace::HslaLinear`].
1188///
1189/// Samplers can be set in animations using the [`Var::easing_with`] method.
1190///
1191/// [`Var::easing_with`]: zng_var::Var::easing_with
1192pub fn hsla_linear_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1193    with_lerp_space(LerpSpace::HslaLinear, || t.sample(step))
1194}
1195
1196context_local! {
1197    static LERP_SPACE: LerpSpace = LerpSpace::default();
1198}
1199
1200#[cfg(test)]
1201mod tests {
1202    use super::*;
1203    use zng_layout::unit::AngleUnits as _;
1204
1205    #[test]
1206    fn hsl_red() {
1207        assert_eq!(Rgba::from(hsl(0.0.deg(), 100.pct(), 50.pct())), rgb(1.0, 0.0, 0.0))
1208    }
1209
1210    #[test]
1211    fn hsl_color() {
1212        assert_eq!(Rgba::from(hsl(91.0.deg(), 1.0, 0.5)), rgb(123, 255, 0))
1213    }
1214
1215    #[test]
1216    fn rgb_to_hsl() {
1217        let color = rgba(0, 100, 200, 0.2);
1218        let a = format!("{color:?}");
1219        let b = format!("{:?}", Rgba::from(Hsla::from(color)));
1220        assert_eq!(a, b)
1221    }
1222
1223    #[test]
1224    fn rgb_to_hsv() {
1225        let color = rgba(0, 100, 200, 0.2);
1226        let a = format!("{color:?}");
1227        let b = format!("{:?}", Rgba::from(Hsva::from(color)));
1228        assert_eq!(a, b)
1229    }
1230
1231    #[test]
1232    fn rgba_display() {
1233        macro_rules! test {
1234            ($($tt:tt)+) => {
1235                let expected = stringify!($($tt)+).replace(" ", "");
1236                let actual = hex!($($tt)+).to_string();
1237                assert_eq!(expected, actual);
1238            }
1239        }
1240
1241        test!(#AABBCC);
1242        test!(#123456);
1243        test!(#000000);
1244        test!(#FFFFFF);
1245
1246        test!(#AABBCCDD);
1247        test!(#12345678);
1248        test!(#00000000);
1249        test!(#FFFFFF00);
1250    }
1251
1252    #[test]
1253    fn test_hex_color() {
1254        fn f(n: u8) -> f32 {
1255            n as f32 / 255.0
1256        }
1257        assert_eq!(Rgba::new(f(0x11), f(0x22), f(0x33), f(0x44)), hex!(0x11223344));
1258
1259        assert_eq!(colors::BLACK, hex!(0x00_00_00_FF));
1260        assert_eq!(colors::WHITE, hex!(0xFF_FF_FF_FF));
1261        assert_eq!(colors::WHITE, hex!(0xFF_FF_FF));
1262        assert_eq!(colors::WHITE, hex!(0xFFFFFF));
1263        assert_eq!(colors::WHITE, hex!(#FFFFFF));
1264        assert_eq!(colors::WHITE, hex!(FFFFFF));
1265        assert_eq!(colors::WHITE, hex!(0xFFFF));
1266        assert_eq!(colors::BLACK, hex!(0x000));
1267        assert_eq!(colors::BLACK, hex!(#000));
1268        assert_eq!(colors::BLACK, hex!(000));
1269    }
1270
1271    // #[test]
1272    // fn rgb_to_hsv_all() {
1273    //     for r in 0..=255 {
1274    //         println!("{r}");
1275    //         for g in 0..=255 {
1276    //             for b in 0..=255 {
1277    //                 let color = rgb(r, g, b);
1278    //                 let a = color.to_string();
1279    //                 let b = color.to_hsva().to_rgba().to_string();
1280    //                 assert_eq!(a, b)
1281    //             }
1282    //         }
1283    //     }
1284    // }
1285}