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#![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#[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
79pub 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#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
97pub struct PreMulRgba {
98 pub red: f32,
100 pub green: f32,
102 pub blue: f32,
104 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#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
183pub struct Hsla {
184 pub hue: f32,
186 pub saturation: f32,
188 pub lightness: f32,
190 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 pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
228 self.hue = hue.into().modulo().0
229 }
230
231 pub fn set_lightness<L: Into<Factor>>(&mut self, lightness: L) {
233 self.lightness = lightness.into().0;
234 }
235
236 pub fn set_saturation<S: Into<Factor>>(&mut self, saturation: S) {
238 self.saturation = saturation.into().0;
239 }
240
241 pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
243 self.alpha = alpha.into().0
244 }
245
246 pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
248 self.set_hue(hue);
249 self
250 }
251
252 pub fn with_lightness<L: Into<Factor>>(mut self, lightness: L) -> Self {
254 self.set_lightness(lightness);
255 self
256 }
257
258 pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
260 self.set_saturation(saturation);
261 self
262 }
263
264 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 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 pub fn is_chromatic(self) -> bool {
288 self.saturation > 0.0001
289 }
290
291 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 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#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
382pub struct Hsva {
383 pub hue: f32,
385 pub saturation: f32,
387 pub value: f32,
389 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 pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
427 self.hue = hue.into().modulo().0
428 }
429
430 pub fn set_value<L: Into<Factor>>(&mut self, value: L) {
432 self.value = value.into().0;
433 }
434
435 pub fn set_saturation<L: Into<Factor>>(&mut self, saturation: L) {
437 self.saturation = saturation.into().0;
438 }
439
440 pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
442 self.alpha = alpha.into().0
443 }
444
445 pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
447 self.set_hue(hue);
448 self
449 }
450
451 pub fn with_value<V: Into<Factor>>(mut self, value: V) -> Self {
453 self.set_value(value);
454 self
455 }
456
457 pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
459 self.set_saturation(saturation);
460 self
461 }
462
463 pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
465 self.set_alpha(alpha);
466 self
467 }
468
469 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
713fn clamp_normal(i: f32) -> f32 {
715 i.clamp(0.0, 1.0)
716}
717
718pub fn rgb<C: Into<RgbaComponent>>(red: C, green: C, blue: C) -> Rgba {
734 rgba(red, green, blue, 1.0)
735}
736
737pub 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
764pub fn hsl<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, lightness: N) -> Hsla {
785 hsla(hue, saturation, lightness, 1.0)
786}
787
788pub 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
820pub fn hsv<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, value: N) -> Hsva {
841 hsva(hue, saturation, value, 1.0)
842}
843
844pub 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 pub static COLOR_SCHEME_VAR: ColorScheme = ColorScheme::default();
879}
880
881pub 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#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
903pub struct LightDark {
904 pub dark: Rgba,
906 pub light: Rgba,
908}
909impl_from_and_into_var! {
910 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 fn from(color: Rgba) -> LightDark {
920 LightDark { dark: color, light: color }
921 }
922
923 fn from(color: Hsva) -> LightDark {
925 Rgba::from(color).into()
926 }
927
928 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 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 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 pub fn shade(self, shade: i8) -> Self {
971 self.shade_fct(shade as f32 * 0.08)
972 }
973
974 pub fn rgba(self) -> Var<Rgba> {
978 IntoVar::<Rgba>::into_var(self)
979 }
980
981 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 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
1016pub trait LightDarkVarExt {
1018 fn rgba(&self) -> Var<Rgba>;
1020 fn rgba_map<T: VarValue>(&self, map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T>;
1022 fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T>;
1024
1025 fn map_rgba(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba>;
1027 fn map_rgba_into<T: VarValue + From<Rgba>>(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T>;
1029
1030 fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba>;
1032 fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T>;
1034
1035 fn shade(&self, shade: i8) -> Var<Rgba>;
1040 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#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1143pub enum LerpSpace {
1144 Rgba,
1146 Hsla,
1148 #[default]
1151 HslaChromatic,
1152 HslaLinear,
1154}
1155
1156pub fn lerp_space() -> LerpSpace {
1161 LERP_SPACE.get_clone()
1162}
1163
1164pub 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
1171pub fn rgba_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1177 with_lerp_space(LerpSpace::Rgba, || t.sample(step))
1178}
1179
1180pub fn hsla_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1184 with_lerp_space(LerpSpace::Hsla, || t.sample(step))
1185}
1186
1187pub 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 }