zng_color/
lib.rs

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