1use crate::{MotionValue, Transition};
3use std::time::Duration;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub struct Color {
8 linear: [f64; 4],
9}
10
11impl Color {
12 pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Self {
14 assert!(
15 [r, g, b, a].iter().all(|v| (0.0..=1.0).contains(v)),
16 "invalid color"
17 );
18 let linear = |v: f64| {
19 if v <= 0.04045 {
20 v / 12.92
21 } else {
22 ((v + 0.055) / 1.055).powf(2.4)
23 }
24 };
25
26 Self {
27 linear: [linear(r), linear(g), linear(b), a],
28 }
29 }
30
31 pub fn rgb(hex: u32) -> Self {
33 Self::rgba(
34 f64::from((hex >> 16) & 255) / 255.0,
35 f64::from((hex >> 8) & 255) / 255.0,
36 f64::from(hex & 255) / 255.0,
37 1.0,
38 )
39 }
40
41 pub fn channels(self) -> [f32; 4] {
43 let srgb = |v: f64| {
44 let v = v.clamp(0.0, 1.0);
45
46 if v <= 0.0031308 {
47 12.92 * v
48 } else {
49 1.055 * v.powf(1.0 / 2.4) - 0.055
50 }
51 };
52
53 [
54 srgb(self.linear[0]) as f32,
55 srgb(self.linear[1]) as f32,
56 srgb(self.linear[2]) as f32,
57 self.linear[3].clamp(0.0, 1.0) as f32,
58 ]
59 }
60
61 pub fn mix(self, other: Self, progress: f64) -> Self {
63 assert!(progress.is_finite(), "invalid color progress");
64 let t = progress.clamp(0.0, 1.0);
65
66 Self {
67 linear: std::array::from_fn(|ix| {
68 self.linear[ix] + (other.linear[ix] - self.linear[ix]) * t
69 }),
70 }
71 }
72}
73
74#[derive(Debug)]
76pub struct ColorState {
77 channels: [MotionValue; 4],
78}
79
80impl ColorState {
81 pub fn new(color: Color) -> Self {
83 Self {
84 channels: color.linear.map(MotionValue::new),
85 }
86 }
87
88 pub fn sample(
90 &mut self,
91 color: Color,
92 transition: Transition,
93 now: Duration,
94 reduced_motion: bool,
95 ) -> (Color, bool) {
96 let mut active = false;
97
98 let linear = std::array::from_fn(|ix| {
99 self.channels[ix].animate(color.linear[ix], transition, now);
100 let frame = self.channels[ix].sample(now, reduced_motion);
101
102 active |= frame.is_animating();
103
104 frame.value()
105 });
106
107 (Color { linear }, active)
108 }
109}
110
111impl crate::MotionData<4> for Color {
112 fn channels(self) -> [f64; 4] {
113 self.linear
114 }
115
116 fn with_channels(self, values: [f64; 4]) -> Self {
117 Self { linear: values }
118 }
119
120 fn compatible(self, _: Self) -> bool {
121 true
122 }
123}