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}
319impl fmt::Debug for Hsla {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 if f.alternate() {
322 f.debug_struct("Hsla")
323 .field("hue", &self.hue)
324 .field("saturation", &self.saturation)
325 .field("lightness", &self.lightness)
326 .field("alpha", &self.alpha)
327 .finish()
328 } else {
329 fn p(n: f32) -> f32 {
330 clamp_normal(n) * 100.0
331 }
332 let a = p(self.alpha);
333 let h = AngleDegree(self.hue).modulo().0.round();
334 if (a - 100.0).abs() <= EQ_GRANULARITY {
335 write!(f, "hsl({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.lightness))
336 } else {
337 write!(
338 f,
339 "hsla({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
340 p(self.saturation),
341 p(self.lightness),
342 a
343 )
344 }
345 }
346 }
347}
348impl fmt::Display for Hsla {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 fn p(n: f32) -> f32 {
351 clamp_normal(n) * 100.0
352 }
353 let a = p(self.alpha);
354 let h = AngleDegree(self.hue).modulo().0.round();
355 if (a - 100.0).abs() <= EQ_GRANULARITY {
356 write!(f, "hsl({h}º, {}%, {}%)", p(self.saturation), p(self.lightness))
357 } else {
358 write!(f, "hsla({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.lightness), a)
359 }
360 }
361}
362impl Transitionable for Hsla {
363 fn lerp(self, to: &Self, step: EasingStep) -> Self {
364 self.lerp(*to, step)
365 }
366}
367
368#[derive(Copy, Clone, serde::Serialize, serde::Deserialize)]
377pub struct Hsva {
378 pub hue: f32,
380 pub saturation: f32,
382 pub value: f32,
384 pub alpha: f32,
386}
387impl PartialEq for Hsva {
388 fn eq(&self, other: &Self) -> bool {
389 about_eq(self.hue, other.hue, EQ_GRANULARITY_100)
390 && about_eq(self.saturation, other.saturation, EQ_GRANULARITY)
391 && about_eq(self.value, other.value, EQ_GRANULARITY)
392 && about_eq(self.alpha, other.alpha, EQ_GRANULARITY)
393 }
394}
395impl Eq for Hsva {}
396impl PartialOrd for Hsva {
397 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
398 Some(self.cmp(other))
399 }
400}
401impl Ord for Hsva {
402 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
403 about_eq_ord(self.hue, other.hue, EQ_GRANULARITY_100)
404 .cmp(&about_eq_ord(self.saturation, other.saturation, EQ_GRANULARITY))
405 .cmp(&about_eq_ord(self.value, other.value, EQ_GRANULARITY))
406 .cmp(&about_eq_ord(self.alpha, other.alpha, EQ_GRANULARITY))
407 }
408}
409impl std::hash::Hash for Hsva {
410 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
411 about_eq_hash(self.hue, EQ_GRANULARITY_100, state);
412 about_eq_hash(self.saturation, EQ_GRANULARITY, state);
413 about_eq_hash(self.value, EQ_GRANULARITY, state);
414 about_eq_hash(self.alpha, EQ_GRANULARITY, state);
415 }
416}
417impl Hsva {
418 pub fn set_hue<H: Into<AngleDegree>>(&mut self, hue: H) {
422 self.hue = hue.into().modulo().0
423 }
424
425 pub fn set_value<L: Into<Factor>>(&mut self, value: L) {
427 self.value = value.into().0;
428 }
429
430 pub fn set_saturation<L: Into<Factor>>(&mut self, saturation: L) {
432 self.saturation = saturation.into().0;
433 }
434
435 pub fn set_alpha<A: Into<Factor>>(&mut self, alpha: A) {
437 self.alpha = alpha.into().0
438 }
439
440 pub fn with_hue<H: Into<AngleDegree>>(mut self, hue: H) -> Self {
442 self.set_hue(hue);
443 self
444 }
445
446 pub fn with_value<V: Into<Factor>>(mut self, value: V) -> Self {
448 self.set_value(value);
449 self
450 }
451
452 pub fn with_saturation<S: Into<Factor>>(mut self, saturation: S) -> Self {
454 self.set_saturation(saturation);
455 self
456 }
457
458 pub fn with_alpha<A: Into<Factor>>(mut self, alpha: A) -> Self {
460 self.set_alpha(alpha);
461 self
462 }
463}
464impl fmt::Debug for Hsva {
465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466 if f.alternate() {
467 f.debug_struct("Hsla")
468 .field("hue", &self.hue)
469 .field("saturation", &self.saturation)
470 .field("value", &self.value)
471 .field("alpha", &self.alpha)
472 .finish()
473 } else {
474 fn p(n: f32) -> f32 {
475 clamp_normal(n) * 100.0
476 }
477 let a = p(self.alpha);
478 let h = AngleDegree(self.hue).modulo().0.round();
479 if (a - 100.0).abs() <= EQ_GRANULARITY {
480 write!(f, "hsv({h}.deg(), {}.pct(), {}.pct())", p(self.saturation), p(self.value))
481 } else {
482 write!(
483 f,
484 "hsva({h}.deg(), {}.pct(), {}.pct(), {}.pct())",
485 p(self.saturation),
486 p(self.value),
487 a
488 )
489 }
490 }
491 }
492}
493impl fmt::Display for Hsva {
494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495 fn p(n: f32) -> f32 {
496 clamp_normal(n) * 100.0
497 }
498 let a = p(self.alpha);
499 let h = AngleDegree(self.hue).modulo().0.round();
500 if (a - 100.0).abs() <= EQ_GRANULARITY {
501 write!(f, "hsv({h}º, {}%, {}%)", p(self.saturation), p(self.value))
502 } else {
503 write!(f, "hsva({h}º, {}%, {}%, {}%)", p(self.saturation), p(self.value), a)
504 }
505 }
506}
507impl_from_and_into_var! {
508 fn from(hsla: Hsla) -> Hsva {
509 let lightness = clamp_normal(hsla.lightness);
510 let saturation = clamp_normal(hsla.saturation);
511
512 let value = lightness + saturation * lightness.min(1.0 - lightness);
513 let saturation = if value <= EQ_GRANULARITY {
514 0.0
515 } else {
516 2.0 * (1.0 - lightness / value)
517 };
518
519 Hsva {
520 hue: hsla.hue,
521 saturation,
522 value,
523 alpha: hsla.alpha,
524 }
525 }
526
527 fn from(hsva: Hsva) -> Hsla {
528 let saturation = clamp_normal(hsva.saturation);
529 let value = clamp_normal(hsva.value);
530
531 let lightness = value * (1.0 - saturation / 2.0);
532 let saturation = if lightness <= EQ_GRANULARITY || lightness >= 1.0 - EQ_GRANULARITY {
533 0.0
534 } else {
535 2.0 * (1.0 * lightness / value)
536 };
537
538 Hsla {
539 hue: hsva.hue,
540 saturation,
541 lightness,
542 alpha: hsva.alpha,
543 }
544 }
545
546 fn from(hsva: Hsva) -> Rgba {
547 let hue = AngleDegree(hsva.hue).modulo().0;
548 let saturation = clamp_normal(hsva.saturation);
549 let value = clamp_normal(hsva.value);
550
551 let c = value * saturation;
552 let hue = hue / 60.0;
553 let x = c * (1.0 - (hue.rem_euclid(2.0) - 1.0).abs());
554
555 let (red, green, blue) = if hue <= 1.0 {
556 (c, x, 0.0)
557 } else if hue <= 2.0 {
558 (x, c, 0.0)
559 } else if hue <= 3.0 {
560 (0.0, c, x)
561 } else if hue <= 4.0 {
562 (0.0, x, c)
563 } else if hue <= 5.0 {
564 (x, 0.0, c)
565 } else if hue <= 6.0 {
566 (c, 0.0, x)
567 } else {
568 (0.0, 0.0, 0.0)
569 };
570
571 let m = value - c;
572
573 let f = |n: f32| ((n + m) * 255.0).round() / 255.0;
574
575 Rgba {
576 red: f(red),
577 green: f(green),
578 blue: f(blue),
579 alpha: hsva.alpha,
580 }
581 }
582
583 fn from(hsla: Hsla) -> Rgba {
584 if hsla.saturation <= EQ_GRANULARITY {
585 return rgba(hsla.lightness, hsla.lightness, hsla.lightness, hsla.alpha);
586 }
587
588 let hue = AngleDegree(hsla.hue).modulo().0;
589 let saturation = clamp_normal(hsla.saturation);
590 let lightness = clamp_normal(hsla.lightness);
591
592 let c = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation;
593 let hp = hue / 60.0;
594 let x = c * (1.0 - ((hp % 2.0) - 1.0).abs());
595 let (red, green, blue) = if hp <= 1.0 {
596 (c, x, 0.0)
597 } else if hp <= 2.0 {
598 (x, c, 0.0)
599 } else if hp <= 3.0 {
600 (0.0, c, x)
601 } else if hp <= 4.0 {
602 (0.0, x, c)
603 } else if hp <= 5.0 {
604 (x, 0.0, c)
605 } else if hp <= 6.0 {
606 (c, 0.0, x)
607 } else {
608 (0.0, 0.0, 0.0)
609 };
610 let m = lightness - c * 0.5;
611
612 let f = |i: f32| ((i + m) * 255.0).round() / 255.0;
613
614 Rgba {
615 red: f(red),
616 green: f(green),
617 blue: f(blue),
618 alpha: hsla.alpha,
619 }
620 }
621}
622impl Transitionable for Hsva {
623 fn lerp(self, to: &Self, step: EasingStep) -> Self {
624 match lerp_space() {
625 LerpSpace::HslaChromatic => Hsla::from(self).slerp_chromatic((*to).into(), step).into(),
626 LerpSpace::Rgba => lerp_rgba_linear(self.into(), (*to).into(), step).into(),
627 LerpSpace::Hsla => Hsla::from(self).slerp((*to).into(), step).into(),
628 LerpSpace::HslaLinear => Hsla::from(self).lerp_hsla((*to).into(), step).into(),
629 }
630 }
631}
632
633macro_rules! cylindrical_color {
634 ($rgba:ident -> ($min:ident, $max:ident, $delta:ident, $hue:ident)) => {
635 fn sanitize(i: f32) -> f32 {
636 clamp_normal((i * 255.0).round() / 255.0)
637 }
638
639 let r = sanitize($rgba.red);
640 let g = sanitize($rgba.green);
641 let b = sanitize($rgba.blue);
642
643 let $min = r.min(g).min(b);
644 let $max = r.max(g).max(b);
645
646 fn about_eq(a: f32, b: f32) -> bool {
647 (a - b) <= EQ_GRANULARITY
648 }
649
650 let $delta = $max - $min;
651
652 let $hue = if $delta <= EQ_GRANULARITY {
653 0.0
654 } else {
655 60.0 * if about_eq($max, r) {
656 ((g - b) / $delta).rem_euclid(6.0)
657 } else if about_eq($max, g) {
658 (b - r) / $delta + 2.0
659 } else {
660 debug_assert!(about_eq($max, b));
661 (r - g) / $delta + 4.0
662 }
663 };
664 };
665}
666
667impl_from_and_into_var! {
668 fn from(rgba: Rgba) -> Hsva {
669 cylindrical_color!(rgba -> (min, max, delta, hue));
670
671 let saturation = if max <= EQ_GRANULARITY { 0.0 } else { delta / max };
672
673 let value = max;
674
675 Hsva {
676 hue,
677 saturation,
678 value,
679 alpha: rgba.alpha,
680 }
681 }
682
683 fn from(rgba: Rgba) -> Hsla {
684 cylindrical_color!(rgba -> (min, max, delta, hue));
685
686 let lightness = (max + min) / 2.0;
687
688 let saturation = if delta <= EQ_GRANULARITY {
689 0.0
690 } else {
691 delta / (1.0 - (2.0 * lightness - 1.0).abs())
692 };
693
694 Hsla {
695 hue,
696 lightness,
697 saturation,
698 alpha: rgba.alpha,
699 }
700 }
701}
702
703fn clamp_normal(i: f32) -> f32 {
705 i.clamp(0.0, 1.0)
706}
707
708pub fn rgb<C: Into<RgbaComponent>>(red: C, green: C, blue: C) -> Rgba {
724 rgba(red, green, blue, 1.0)
725}
726
727pub fn rgba<C: Into<RgbaComponent>, A: Into<RgbaComponent>>(red: C, green: C, blue: C, alpha: A) -> Rgba {
746 Rgba {
747 red: red.into().0,
748 green: green.into().0,
749 blue: blue.into().0,
750 alpha: alpha.into().0,
751 }
752}
753
754pub fn hsl<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, lightness: N) -> Hsla {
775 hsla(hue, saturation, lightness, 1.0)
776}
777
778pub fn hsla<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, lightness: N, alpha: A) -> Hsla {
800 Hsla {
801 hue: hue.into().0,
802 saturation: saturation.into().0,
803 lightness: lightness.into().0,
804 alpha: alpha.into().0,
805 }
806}
807
808pub fn hsv<H: Into<AngleDegree>, N: Into<Factor>>(hue: H, saturation: N, value: N) -> Hsva {
829 hsva(hue, saturation, value, 1.0)
830}
831
832pub fn hsva<H: Into<AngleDegree>, N: Into<Factor>, A: Into<Factor>>(hue: H, saturation: N, value: N, alpha: A) -> Hsva {
854 Hsva {
855 hue: hue.into().0,
856 saturation: saturation.into().0,
857 value: value.into().0,
858 alpha: alpha.into().0,
859 }
860}
861
862context_var! {
863 pub static COLOR_SCHEME_VAR: ColorScheme = ColorScheme::default();
865}
866
867pub fn light_dark(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> LightDark {
877 LightDark {
878 light: light.into(),
879 dark: dark.into(),
880 }
881}
882
883#[derive(Debug, Clone, Copy, PartialEq, Hash, serde::Serialize, serde::Deserialize, Transitionable)]
889pub struct LightDark {
890 pub dark: Rgba,
892 pub light: Rgba,
894}
895impl_from_and_into_var! {
896 fn from<L: Into<Rgba>, D: Into<Rgba>>((light, dark): (L, D)) -> LightDark {
898 LightDark {
899 light: light.into(),
900 dark: dark.into(),
901 }
902 }
903
904 fn from(color: Rgba) -> LightDark {
906 LightDark { dark: color, light: color }
907 }
908
909 fn from(color: Hsva) -> LightDark {
911 Rgba::from(color).into()
912 }
913
914 fn from(color: Hsla) -> LightDark {
916 Rgba::from(color).into()
917 }
918
919 fn from(color: LightDark) -> Option<LightDark>;
920}
921impl IntoVar<Rgba> for LightDark {
922 fn into_var(self) -> Var<Rgba> {
923 COLOR_SCHEME_VAR.map(move |s| match s {
924 ColorScheme::Light => self.light,
925 ColorScheme::Dark => self.dark,
926 _ => self.light,
927 })
928 }
929}
930impl LightDark {
931 pub fn new(light: impl Into<Rgba>, dark: impl Into<Rgba>) -> Self {
933 Self {
934 light: light.into(),
935 dark: dark.into(),
936 }
937 }
938
939 pub fn shade_fct(mut self, factor: impl Into<Factor>) -> Self {
941 let mut factor = factor.into();
942 let (dark_overlay, light_overlay) = if factor > 0.fct() {
943 (colors::WHITE, colors::BLACK)
944 } else {
945 factor = factor.abs();
946 (colors::BLACK, colors::WHITE)
947 };
948 self.dark = dark_overlay.with_alpha(factor).mix_normal(self.dark);
949 self.light = light_overlay.with_alpha(factor).mix_normal(self.light);
950 self
951 }
952
953 pub fn shade(self, shade: i8) -> Self {
957 self.shade_fct(shade as f32 * 0.08)
958 }
959
960 pub fn rgba(self) -> Var<Rgba> {
964 IntoVar::<Rgba>::into_var(self)
965 }
966
967 pub fn rgba_map<T: VarValue>(self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T> {
969 COLOR_SCHEME_VAR.map(move |s| match s {
970 ColorScheme::Light => map(self.light),
971 ColorScheme::Dark => map(self.dark),
972 _ => map(self.light),
973 })
974 }
975
976 pub fn rgba_into<T: VarValue + From<Rgba>>(self) -> Var<T> {
978 self.rgba_map(T::from)
979 }
980}
981impl ops::Index<ColorScheme> for LightDark {
982 type Output = Rgba;
983
984 fn index(&self, index: ColorScheme) -> &Self::Output {
985 match index {
986 ColorScheme::Light => &self.light,
987 ColorScheme::Dark => &self.dark,
988 _ => &self.light,
989 }
990 }
991}
992impl ops::IndexMut<ColorScheme> for LightDark {
993 fn index_mut(&mut self, index: ColorScheme) -> &mut Self::Output {
994 match index {
995 ColorScheme::Light => &mut self.light,
996 ColorScheme::Dark => &mut self.dark,
997 _ => &mut self.light,
998 }
999 }
1000}
1001
1002pub trait LightDarkVarExt {
1004 fn rgba(&self) -> Var<Rgba>;
1006 fn rgba_map<T: VarValue>(&self, map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T>;
1008 fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T>;
1010
1011 fn map_rgba(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba>;
1013 fn map_rgba_into<T: VarValue + From<Rgba>>(&self, map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T>;
1015
1016 fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba>;
1018 fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T>;
1020
1021 fn shade(&self, shade: i8) -> Var<Rgba>;
1026 fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> Var<T>;
1028}
1029impl LightDarkVarExt for Var<LightDark> {
1030 fn rgba(&self) -> Var<Rgba> {
1031 expr_var! {
1032 let c = #{self.clone()};
1033 match *#{COLOR_SCHEME_VAR} {
1034 ColorScheme::Light => c.light,
1035 ColorScheme::Dark => c.dark,
1036 _ => c.light,
1037 }
1038 }
1039 }
1040
1041 fn rgba_map<T: VarValue>(&self, mut map: impl FnMut(Rgba) -> T + Send + 'static) -> Var<T> {
1042 expr_var! {
1043 let c = #{self.clone()};
1044 match *#{COLOR_SCHEME_VAR} {
1045 ColorScheme::Light => map(c.light),
1046 ColorScheme::Dark => map(c.dark),
1047 _ => map(c.light),
1048 }
1049 }
1050 }
1051
1052 fn rgba_into<T: VarValue + From<Rgba>>(&self) -> Var<T> {
1053 self.rgba_map(Into::into)
1054 }
1055
1056 fn map_rgba(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<Rgba> {
1057 expr_var! {
1058 let c = map(*#{self.clone()});
1059 match *#{COLOR_SCHEME_VAR} {
1060 ColorScheme::Light => c.light,
1061 ColorScheme::Dark => c.dark,
1062 _ => c.light,
1063 }
1064 }
1065 }
1066
1067 fn map_rgba_into<T: VarValue + From<Rgba>>(&self, mut map: impl FnMut(LightDark) -> LightDark + Send + 'static) -> Var<T> {
1068 expr_var! {
1069 let c = map(*#{self.clone()});
1070 match *#{COLOR_SCHEME_VAR} {
1071 ColorScheme::Light => T::from(c.light),
1072 ColorScheme::Dark => T::from(c.dark),
1073 _ => T::from(c.light),
1074 }
1075 }
1076 }
1077
1078 fn shade_fct(&self, fct: impl Into<Factor>) -> Var<Rgba> {
1079 let fct = fct.into();
1080 expr_var! {
1081 let c = #{self.clone()}.shade_fct(fct);
1082 match *#{COLOR_SCHEME_VAR} {
1083 ColorScheme::Light => c.light,
1084 ColorScheme::Dark => c.dark,
1085 _ => c.light,
1086 }
1087 }
1088 }
1089
1090 fn shade(&self, shade: i8) -> Var<Rgba> {
1091 expr_var! {
1092 let c = #{self.clone()}.shade(shade);
1093 match *#{COLOR_SCHEME_VAR} {
1094 ColorScheme::Light => c.light,
1095 ColorScheme::Dark => c.dark,
1096 _ => c.light,
1097 }
1098 }
1099 }
1100
1101 fn shade_fct_into<T: VarValue + From<Rgba>>(&self, fct: impl Into<Factor>) -> Var<T> {
1102 let fct = fct.into();
1103 expr_var! {
1104 let c = #{self.clone()}.shade_fct(fct);
1105 match *#{COLOR_SCHEME_VAR} {
1106 ColorScheme::Light => T::from(c.light),
1107 ColorScheme::Dark => T::from(c.dark),
1108 _ => T::from(c.light),
1109 }
1110 }
1111 }
1112
1113 fn shade_into<T: VarValue + From<Rgba>>(&self, shade: i8) -> Var<T> {
1114 expr_var! {
1115 let c = #{self.clone()}.shade(shade);
1116 match *#{COLOR_SCHEME_VAR} {
1117 ColorScheme::Light => T::from(c.light),
1118 ColorScheme::Dark => T::from(c.dark),
1119 _ => T::from(c.light),
1120 }
1121 }
1122 }
1123}
1124
1125#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1129pub enum LerpSpace {
1130 Rgba,
1132 Hsla,
1134 #[default]
1137 HslaChromatic,
1138 HslaLinear,
1140}
1141
1142pub fn lerp_space() -> LerpSpace {
1147 LERP_SPACE.get_clone()
1148}
1149
1150pub fn with_lerp_space<R>(space: LerpSpace, f: impl FnOnce() -> R) -> R {
1154 LERP_SPACE.with_context(&mut Some(Arc::new(space)), f)
1155}
1156
1157pub fn rgba_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1163 with_lerp_space(LerpSpace::Rgba, || t.sample(step))
1164}
1165
1166pub fn hsla_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1170 with_lerp_space(LerpSpace::Hsla, || t.sample(step))
1171}
1172
1173pub fn hsla_linear_sampler<T: Transitionable>(t: &Transition<T>, step: EasingStep) -> T {
1179 with_lerp_space(LerpSpace::HslaLinear, || t.sample(step))
1180}
1181
1182context_local! {
1183 static LERP_SPACE: LerpSpace = LerpSpace::default();
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188 use super::*;
1189 use zng_layout::unit::AngleUnits as _;
1190
1191 #[test]
1192 fn hsl_red() {
1193 assert_eq!(Rgba::from(hsl(0.0.deg(), 100.pct(), 50.pct())), rgb(1.0, 0.0, 0.0))
1194 }
1195
1196 #[test]
1197 fn hsl_color() {
1198 assert_eq!(Rgba::from(hsl(91.0.deg(), 1.0, 0.5)), rgb(123, 255, 0))
1199 }
1200
1201 #[test]
1202 fn rgb_to_hsl() {
1203 let color = rgba(0, 100, 200, 0.2);
1204 let a = format!("{color:?}");
1205 let b = format!("{:?}", Rgba::from(Hsla::from(color)));
1206 assert_eq!(a, b)
1207 }
1208
1209 #[test]
1210 fn rgb_to_hsv() {
1211 let color = rgba(0, 100, 200, 0.2);
1212 let a = format!("{color:?}");
1213 let b = format!("{:?}", Rgba::from(Hsva::from(color)));
1214 assert_eq!(a, b)
1215 }
1216
1217 #[test]
1218 fn rgba_display() {
1219 macro_rules! test {
1220 ($($tt:tt)+) => {
1221 let expected = stringify!($($tt)+).replace(" ", "");
1222 let actual = hex!($($tt)+).to_string();
1223 assert_eq!(expected, actual);
1224 }
1225 }
1226
1227 test!(#AABBCC);
1228 test!(#123456);
1229 test!(#000000);
1230 test!(#FFFFFF);
1231
1232 test!(#AABBCCDD);
1233 test!(#12345678);
1234 test!(#00000000);
1235 test!(#FFFFFF00);
1236 }
1237
1238 #[test]
1239 fn test_hex_color() {
1240 fn f(n: u8) -> f32 {
1241 n as f32 / 255.0
1242 }
1243 assert_eq!(Rgba::new(f(0x11), f(0x22), f(0x33), f(0x44)), hex!(0x11223344));
1244
1245 assert_eq!(colors::BLACK, hex!(0x00_00_00_FF));
1246 assert_eq!(colors::WHITE, hex!(0xFF_FF_FF_FF));
1247 assert_eq!(colors::WHITE, hex!(0xFF_FF_FF));
1248 assert_eq!(colors::WHITE, hex!(0xFFFFFF));
1249 assert_eq!(colors::WHITE, hex!(#FFFFFF));
1250 assert_eq!(colors::WHITE, hex!(FFFFFF));
1251 assert_eq!(colors::WHITE, hex!(0xFFFF));
1252 assert_eq!(colors::BLACK, hex!(0x000));
1253 assert_eq!(colors::BLACK, hex!(#000));
1254 assert_eq!(colors::BLACK, hex!(000));
1255 }
1256
1257 }