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#![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#[macro_export]
66macro_rules! hex {
67 ($($tt:tt)+) => {
68 $crate::hex_color!{$crate, $($tt)*}
69 };
70}
71
72const EPSILON: f32 = 0.00001;
74const 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
85pub 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#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
103pub struct PreMulRgba {
104 pub red: f32,
106 pub green: f32,
108 pub blue: f32,
110 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#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
175pub struct Hsla {
176 pub hue: f32,
178 pub saturation: f32,
180 pub lightness: f32,
182 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 pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
206 self.hue = hue.into().modulo().0
207 }
208
209 pub fn set_lightness<L: Into<Factor>>(&mut self, lightness: L) {
211 self.lightness = lightness.into().0;
212 }
213
214 pub fn set_saturation<S: Into<Factor>>(&mut self, saturation: S) {
216 self.saturation = saturation.into().0;
217 }
218
219 pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
221 self.alpha = alpha.into().0
222 }
223
224 pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
226 self.set_hue(hue);
227 self
228 }
229
230 pub fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
232 self.set_lightness(lightness);
233 self
234 }
235
236 pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
238 self.set_saturation(saturation);
239 self
240 }
241
242 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 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 pub fn is_chromatic(self) -> bool {
266 self.saturation > 0.0001
267 }
268
269 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#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
355pub struct Hsva {
356 pub hue: f32,
358 pub saturation: f32,
360 pub value: f32,
362 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 pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
386 self.hue = hue.into().modulo().0
387 }
388
389 pub fn set_value<L: Into<Factor>>(&mut self, value: L) {
391 self.value = value.into().0;
392 }
393
394 pub fn set_saturation<L: Into<Factor>>(&mut self, saturation: L) {
396 self.saturation = saturation.into().0;
397 }
398
399 pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
401 self.alpha = alpha.into().0
402 }
403
404 pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
406 self.set_hue(hue);
407 self
408 }
409
410 pub fn with_value<V: Into<Factor>>(mut self, value: V) -> Self {
412 self.set_value(value);
413 self
414 }
415
416 pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
418 self.set_saturation(saturation);
419 self
420 }
421
422 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
663fn clamp_normal(i: f32) -> f32 {
665 i.clamp(0.0, 1.0)
666}
667
668pub fn rgb<C: Into<RgbaComponent>>(red: C, green: C, blue: C) -> Rgba {
684 rgba(red, green, blue, 1.0)
685}
686
687pub 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
714pub fn hsl<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, lightness: N) -> Hsla {
735 hsla(hue, saturation, lightness, 1.0)
736}
737
738pub 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
768pub fn hsv<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, value: N) -> Hsva {
789 hsva(hue, saturation, value, 1.0)
790}
791
792pub 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 pub static COLOR_SCHEME_VAR: ColorScheme = ColorScheme::default();
825}
826
827pub 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#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
849pub struct LightDark {
850 pub dark: Rgba,
852 pub light: Rgba,
854}
855impl_from_and_into_var! {
856 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 fn from(color: Rgba) -> LightDark {
866 LightDark { dark: color, light: color }
867 }
868
869 fn from(color: Hsva) -> LightDark {
871 Rgba::from(color).into()
872 }
873
874 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 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 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 pub fn shade(self, shade: i8) -> Self {
918 self.shade_fct(shade as f32 * 0.08)
919 }
920
921 pub fn rgba(self) -> ContextualizedVar<Rgba> {
925 IntoVar::<Rgba>::into_var(self)
926 }
927
928 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 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
960pub trait LightDarkVarExt {
962 fn rgba(&self) -> impl Var<Rgba>;
964 fn rgba_map<T: VarValue>(&self, map: impl FnMut(Rgba) -> T + Send + 'static) -> impl Var<T>;
966 fn rgba_into<T: VarValue + From<Rgba>>(&self) -> impl Var<T>;
968
969 fn map_rgba(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> impl Var<Rgba>;
971 fn map_rgba_into<T: VarValue + From<Rgba>>(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> impl Var<T>;
973
974 fn shade_fct(&self, fct: impl Into<Factor>) -> impl Var<Rgba>;
976 fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> impl Var<T>;
978
979 fn shade(&self, shade: i8) -> impl Var<Rgba>;
984 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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1079pub enum LerpSpace {
1080 Rgba,
1082 Hsla,
1084 #[default]
1087 HslaChromatic,
1088 HslaLinear,
1090}
1091
1092pub fn lerp_space() -> LerpSpace {
1097 LERP_SPACE.get_clone()
1098}
1099
1100pub 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
1107pub fn rgba_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1113 with_lerp_space(LerpSpace::Rgba, || t.sample(step))
1114}
1115
1116pub fn hsla_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1120 with_lerp_space(LerpSpace::Hsla, || t.sample(step))
1121}
1122
1123pub 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 }