1use embedded_graphics_core::pixelcolor::{Rgb565, RgbColor};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum Easing {
11 #[default]
12 Linear,
13 EaseInCubic,
14 EaseOutCubic,
15 EaseInOutCubic,
16 Smoothstep,
17}
18
19#[inline]
21pub fn apply_easing(t: f32, easing: Easing) -> f32 {
22 let t = t.clamp(0.0, 1.0);
23 match easing {
24 Easing::Linear => t,
25 Easing::EaseInCubic => t * t * t,
26 Easing::EaseOutCubic => {
27 let u = 1.0 - t;
28 1.0 - u * u * u
29 }
30 Easing::EaseInOutCubic => {
31 if t < 0.5 {
32 4.0 * t * t * t
33 } else {
34 let u = -2.0 * t + 2.0;
35 1.0 - u * u * u * 0.5
36 }
37 }
38 Easing::Smoothstep => t * t * (3.0 - 2.0 * t),
39 }
40}
41
42#[inline(always)]
44pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
45 a + (b - a) * t
46}
47
48#[inline(always)]
50pub fn lerp3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
51 [
52 lerp(a[0], b[0], t),
53 lerp(a[1], b[1], t),
54 lerp(a[2], b[2], t),
55 ]
56}
57
58#[derive(Debug, Clone, Copy)]
60pub struct Tween {
61 pub from: f32,
62 pub to: f32,
63 pub duration: f32,
64 pub elapsed: f32,
65 pub easing: Easing,
66}
67
68impl Tween {
69 pub const fn new(from: f32, to: f32, duration: f32, easing: Easing) -> Self {
71 Self {
72 from,
73 to,
74 duration,
75 elapsed: 0.0,
76 easing,
77 }
78 }
79
80 pub fn reset(&mut self) {
82 self.elapsed = 0.0;
83 }
84
85 pub fn advance(&mut self, dt: f32) {
87 if dt > 0.0 {
88 self.elapsed += dt;
89 }
90 }
91
92 #[inline]
94 pub fn progress(&self) -> f32 {
95 if self.duration <= 0.0 {
96 return 1.0;
97 }
98 (self.elapsed / self.duration).min(1.0)
99 }
100
101 #[inline]
103 pub fn value(&self) -> f32 {
104 let t = apply_easing(self.progress(), self.easing);
105 lerp(self.from, self.to, t)
106 }
107
108 #[inline]
110 pub fn is_done(&self) -> bool {
111 self.duration <= 0.0 || self.elapsed >= self.duration
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
117pub struct Tween3 {
118 pub from: [f32; 3],
119 pub to: [f32; 3],
120 pub duration: f32,
121 pub elapsed: f32,
122 pub easing: Easing,
123}
124
125impl Tween3 {
126 pub const fn new(from: [f32; 3], to: [f32; 3], duration: f32, easing: Easing) -> Self {
127 Self {
128 from,
129 to,
130 duration,
131 elapsed: 0.0,
132 easing,
133 }
134 }
135
136 pub fn reset(&mut self) {
137 self.elapsed = 0.0;
138 }
139
140 pub fn advance(&mut self, dt: f32) {
141 if dt > 0.0 {
142 self.elapsed += dt;
143 }
144 }
145
146 #[inline]
147 pub fn progress(&self) -> f32 {
148 if self.duration <= 0.0 {
149 return 1.0;
150 }
151 (self.elapsed / self.duration).min(1.0)
152 }
153
154 #[inline]
155 pub fn value(&self) -> [f32; 3] {
156 let t = apply_easing(self.progress(), self.easing);
157 lerp3(self.from, self.to, t)
158 }
159
160 #[inline]
161 pub fn is_done(&self) -> bool {
162 self.duration <= 0.0 || self.elapsed >= self.duration
163 }
164}
165
166#[inline]
170pub fn scale_rgb565(color: Rgb565, factor: f32) -> Rgb565 {
171 let f = factor.clamp(0.0, 1.0);
172 Rgb565::new(
173 (color.r() as f32 * f) as u8,
174 (color.g() as f32 * f) as u8,
175 (color.b() as f32 * f) as u8,
176 )
177}
178
179#[cfg(test)]
180mod tests {
181 extern crate std;
182
183 use super::*;
184 use embedded_graphics_core::pixelcolor::WebColors;
185
186 #[test]
187 fn test_ease_out_cubic_endpoints() {
188 assert!((apply_easing(0.0, Easing::EaseOutCubic) - 0.0).abs() < 1e-5);
189 assert!((apply_easing(1.0, Easing::EaseOutCubic) - 1.0).abs() < 1e-5);
190 }
191
192 #[test]
193 fn test_tween_completes() {
194 let mut tw = Tween::new(0.0, 10.0, 1.0, Easing::Linear);
195 assert!(!tw.is_done());
196 tw.advance(0.5);
197 assert!((tw.value() - 5.0).abs() < 1e-5);
198 tw.advance(0.5);
199 assert!(tw.is_done());
200 assert!((tw.value() - 10.0).abs() < 1e-5);
201 }
202
203 #[test]
204 fn test_scale_rgb565() {
205 let c = Rgb565::new(30, 60, 30);
206 assert_eq!(scale_rgb565(c, 0.0), Rgb565::BLACK);
207 assert_eq!(scale_rgb565(c, 1.0), c);
208 }
209
210 #[test]
211 fn test_tween3_lerp() {
212 let mut tw = Tween3::new([0.0, 0.0, 0.0], [2.0, 4.0, 6.0], 2.0, Easing::Linear);
213 tw.advance(1.0);
214 let v = tw.value();
215 assert!((v[0] - 1.0).abs() < 1e-5);
216 assert!((v[1] - 2.0).abs() < 1e-5);
217 assert!((v[2] - 3.0).abs() < 1e-5);
218 }
219}