1use crate::easing::*;
4use std::{rc::Rc, sync::OnceLock};
5
6pub type EasingFunction = Rc<dyn Fn(f64) -> f64>;
7
8#[derive(Clone)]
9pub struct KeyFrame {
10 pub time: f64,
11 pub value: f64,
12 pub easing_function: EasingFunction,
14}
15impl KeyFrame {
16 pub fn new(time: f64, value: f64) -> Self {
17 Self {
18 time,
19 value,
20 easing_function: Rc::new(EasingFunctions::lineal),
21 }
22 }
23}
24
25pub struct EasingFunctions;
26impl EasingFunctions {
27 pub fn lineal(t: f64) -> f64 {
28 t
29 }
30 pub fn back_in(t: f64) -> f64 {
31 BackEasingFunction::ease_in(t, 1.70158)
32 }
33 pub fn back_out(t: f64) -> f64 {
34 BackEasingFunction::ease_out(t, 1.70158)
35 }
36 pub fn back_in_out(t: f64) -> f64 {
37 BackEasingFunction::ease_in_out(t, 1.70158)
38 }
39 pub fn bounce_in(t: f64) -> f64 {
40 BounceEasingFunction::ease_in(t)
41 }
42 pub fn bounce_out(t: f64) -> f64 {
43 BounceEasingFunction::ease_out(t)
44 }
45 pub fn bounce_in_out(t: f64) -> f64 {
46 BounceEasingFunction::ease_in_out(t)
47 }
48 pub fn circle_in(t: f64) -> f64 {
49 CircleEasingFunction::ease_in(t)
50 }
51 pub fn circle_out(t: f64) -> f64 {
52 CircleEasingFunction::ease_out(t)
53 }
54 pub fn circle_in_out(t: f64) -> f64 {
55 CircleEasingFunction::ease_in_out(t)
56 }
57 pub fn cubic_in(t: f64) -> f64 {
58 CubicEasingFunction::ease_in(t)
59 }
60 pub fn cubic_out(t: f64) -> f64 {
61 CubicEasingFunction::ease_out(t)
62 }
63 pub fn cubic_in_out(t: f64) -> f64 {
64 CubicEasingFunction::ease_in_out(t)
65 }
66 pub fn elastic_in(t: f64) -> f64 {
67 ElasticEasingFunction::ease_in(t, 1.0, 0.3)
68 }
69 pub fn elastic_out(t: f64) -> f64 {
70 ElasticEasingFunction::ease_out(t, 1.0, 0.3)
71 }
72 pub fn elastic_in_out(t: f64) -> f64 {
73 ElasticEasingFunction::ease_in_out(t, 1.0, 0.3)
74 }
75 pub fn exponential_in(t: f64) -> f64 {
76 ExponentialEasingFunction::ease_in(t)
77 }
78 pub fn exponential_out(t: f64) -> f64 {
79 ExponentialEasingFunction::ease_out(t)
80 }
81 pub fn exponential_in_out(t: f64) -> f64 {
82 ExponentialEasingFunction::ease_in_out(t)
83 }
84 pub fn polinominal_in(t: f64) -> f64 {
85 PolinominalEasingFunction::ease_in(t, 3.0)
86 }
87 pub fn polinominal_out(t: f64) -> f64 {
88 PolinominalEasingFunction::ease_out(t, 3.0)
89 }
90 pub fn polinominal_in_out(t: f64) -> f64 {
91 PolinominalEasingFunction::ease_in_out(t, 3.0)
92 }
93 pub fn quadratic_in(t: f64) -> f64 {
94 t * t
95 }
96 pub fn quadratic_out(t: f64) -> f64 {
97 t * (2.0 - t)
98 }
99 pub fn quadratic_in_out(t: f64) -> f64 {
100 let t = t * 2.0;
101 if t <= 1.0 {
102 t * t / 2.0
103 } else {
104 let t = t - 1.0;
105 (t * (2.0 - t) + 1.0) / 2.0
106 }
107 }
108 pub fn sin_in(t: f64) -> f64 {
109 if t == 1.0 {
110 1.0
111 } else {
112 1.0 - (t * std::f64::consts::FRAC_PI_2).cos()
113 }
114 }
115 pub fn sin_out(t: f64) -> f64 {
116 (t * std::f64::consts::FRAC_PI_2).sin()
117 }
118 pub fn sin_in_out(t: f64) -> f64 {
119 (1.0 - (std::f64::consts::PI * t).cos()) / 2.0
120 }
121 pub fn ease(t: f64) -> f64 {
122 static CURVE: OnceLock<CubicBezierEasingFunction> = OnceLock::new();
123 CURVE
124 .get_or_init(|| {
125 CubicBezierEasingFunction::new(0.25, 0.1, 0.25, 1.0)
126 .expect("constant control points are valid")
127 })
128 .sample(t)
129 }
130 pub fn ease_in(t: f64) -> f64 {
131 static CURVE: OnceLock<CubicBezierEasingFunction> = OnceLock::new();
132 CURVE
133 .get_or_init(|| {
134 CubicBezierEasingFunction::new(0.42, 0.0, 1.0, 1.0)
135 .expect("constant control points are valid")
136 })
137 .sample(t)
138 }
139 pub fn ease_out(t: f64) -> f64 {
140 static CURVE: OnceLock<CubicBezierEasingFunction> = OnceLock::new();
141 CURVE
142 .get_or_init(|| {
143 CubicBezierEasingFunction::new(0.0, 0.0, 0.58, 1.0)
144 .expect("constant control points are valid")
145 })
146 .sample(t)
147 }
148 pub fn ease_in_out(t: f64) -> f64 {
149 static CURVE: OnceLock<CubicBezierEasingFunction> = OnceLock::new();
150 CURVE
151 .get_or_init(|| {
152 CubicBezierEasingFunction::new(0.42, 0.0, 0.58, 1.0)
153 .expect("constant control points are valid")
154 })
155 .sample(t)
156 }
157 pub fn build_custom_back_in(s: f64) -> EasingFunction {
158 Rc::new(move |t| BackEasingFunction::ease_in(t, s))
159 }
160 pub fn build_custom_back_out(s: f64) -> EasingFunction {
161 Rc::new(move |t| BackEasingFunction::ease_out(t, s))
162 }
163 pub fn build_custom_back_in_out(s: f64) -> EasingFunction {
164 Rc::new(move |t| BackEasingFunction::ease_in_out(t, s))
165 }
166 pub fn build_custom_elastic_in(a: f64, p: f64) -> EasingFunction {
167 Rc::new(move |t| ElasticEasingFunction::ease_in(t, a, p))
168 }
169 pub fn build_custom_elastic_out(a: f64, p: f64) -> EasingFunction {
170 Rc::new(move |t| ElasticEasingFunction::ease_out(t, a, p))
171 }
172 pub fn build_custom_elastic_in_out(a: f64, p: f64) -> EasingFunction {
173 Rc::new(move |t| ElasticEasingFunction::ease_in_out(t, a, p))
174 }
175 pub fn build_custom_polinominal_in(e: f64) -> EasingFunction {
176 Rc::new(move |t| PolinominalEasingFunction::ease_in(t, e))
177 }
178 pub fn build_custom_polinominal_out(e: f64) -> EasingFunction {
179 Rc::new(move |t| PolinominalEasingFunction::ease_out(t, e))
180 }
181 pub fn build_custom_polinominal_in_out(e: f64) -> EasingFunction {
182 Rc::new(move |t| PolinominalEasingFunction::ease_in_out(t, e))
183 }
184 pub fn build_cubic_bezier(
185 x1: f64,
186 y1: f64,
187 x2: f64,
188 y2: f64,
189 ) -> Result<EasingFunction, EasingError> {
190 let curve = CubicBezierEasingFunction::new(x1, y1, x2, y2)?;
191 Ok(Rc::new(move |t| curve.sample(t)))
192 }
193 pub fn build_function_using_key_frames(
194 mut frames: Vec<KeyFrame>,
195 ) -> Result<EasingFunction, EasingError> {
196 if frames.len() < 2 {
197 return Err(EasingError::TooFewKeyFrames);
198 }
199 if frames
200 .iter()
201 .any(|f| !f.time.is_finite() || !(0.0..=1.0).contains(&f.time) || !f.value.is_finite())
202 || frames.windows(2).any(|w| w[0].time >= w[1].time)
203 {
204 return Err(EasingError::InvalidKeyFrameTime);
205 }
206 let last = &frames[frames.len() - 1];
207 if last.time < 1.0 {
208 frames.push(KeyFrame::new(1.0, last.value));
209 }
210 Ok(Rc::new(move |t| {
211 let mut i = 0;
212 while i < frames.len() - 2 && frames[i + 1].time < t {
213 i += 1;
214 }
215 let current = &frames[i];
216 let next = &frames[i + 1];
217 let progress = (t - current.time) / (next.time - current.time);
218 current.value + (next.easing_function)(progress) * (next.value - current.value)
219 }))
220 }
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226 #[test]
227 fn all_predefined_easing_boundaries_match_source() {
228 let functions: [fn(f64) -> f64; 32] = [
229 EasingFunctions::lineal,
230 EasingFunctions::back_in,
231 EasingFunctions::back_out,
232 EasingFunctions::back_in_out,
233 EasingFunctions::bounce_in,
234 EasingFunctions::bounce_out,
235 EasingFunctions::bounce_in_out,
236 EasingFunctions::circle_in,
237 EasingFunctions::circle_out,
238 EasingFunctions::circle_in_out,
239 EasingFunctions::cubic_in,
240 EasingFunctions::cubic_out,
241 EasingFunctions::cubic_in_out,
242 EasingFunctions::elastic_in,
243 EasingFunctions::elastic_out,
244 EasingFunctions::elastic_in_out,
245 EasingFunctions::exponential_in,
246 EasingFunctions::exponential_out,
247 EasingFunctions::exponential_in_out,
248 EasingFunctions::polinominal_in,
249 EasingFunctions::polinominal_out,
250 EasingFunctions::polinominal_in_out,
251 EasingFunctions::quadratic_in,
252 EasingFunctions::quadratic_out,
253 EasingFunctions::quadratic_in_out,
254 EasingFunctions::sin_in,
255 EasingFunctions::sin_out,
256 EasingFunctions::sin_in_out,
257 EasingFunctions::ease,
258 EasingFunctions::ease_in,
259 EasingFunctions::ease_out,
260 EasingFunctions::ease_in_out,
261 ];
262 for (i, f) in functions.into_iter().enumerate() {
263 assert!(f(0.0).abs() < 1e-8, "function {i}");
264 assert!((f(1.0) - 1.0).abs() < 1e-8, "function {i}");
265 }
266 assert_eq!(EasingFunctions::lineal(0.5), 0.5);
267 assert_eq!(EasingFunctions::quadratic_in(0.5), 0.25);
268 }
269 #[test]
270 fn customary_easing_curves_are_monotonic() {
271 for f in [
272 EasingFunctions::lineal,
273 EasingFunctions::sin_out,
274 EasingFunctions::cubic_out,
275 EasingFunctions::circle_out,
276 ] {
277 let mut previous = f(0.0);
278 for i in 1..=100 {
279 let current = f(i as f64 / 100.0);
280 assert!(current >= previous);
281 previous = current;
282 }
283 }
284 }
285 #[test]
286 fn custom_easing_parameters_match_source_boundaries() {
287 for f in [
288 EasingFunctions::build_custom_back_in(1.70158),
289 EasingFunctions::build_custom_back_out(1.70158),
290 EasingFunctions::build_custom_back_in_out(1.70158),
291 EasingFunctions::build_custom_elastic_in(1.0, 0.3),
292 EasingFunctions::build_custom_elastic_out(1.0, 0.3),
293 EasingFunctions::build_custom_elastic_in_out(1.0, 0.3),
294 EasingFunctions::build_custom_polinominal_in(4.0),
295 EasingFunctions::build_custom_polinominal_out(4.0),
296 EasingFunctions::build_custom_polinominal_in_out(4.0),
297 ] {
298 assert!(f(0.0).abs() < 1e-8);
299 assert!((f(1.0) - 1.0).abs() < 1e-8);
300 }
301 }
302 #[test]
303 fn key_frames_interpolate_and_extend_final_value() {
304 let f = EasingFunctions::build_function_using_key_frames(vec![
305 KeyFrame::new(0.0, 0.0),
306 KeyFrame::new(1.0, 100.0),
307 ])
308 .unwrap();
309 assert_eq!((f(0.0), f(0.5), f(1.0)), (0.0, 50.0, 100.0));
310 let f = EasingFunctions::build_function_using_key_frames(vec![
311 KeyFrame::new(0.0, 0.0),
312 KeyFrame::new(0.5, 50.0),
313 ])
314 .unwrap();
315 assert_eq!(f(1.0), 50.0);
316 }
317 #[test]
318 fn key_frames_use_destination_easing_and_reject_undefined_segments() {
319 let f = EasingFunctions::build_function_using_key_frames(vec![
320 KeyFrame::new(0.0, 0.0),
321 KeyFrame {
322 time: 1.0,
323 value: 100.0,
324 easing_function: Rc::new(EasingFunctions::quadratic_in),
325 },
326 ])
327 .unwrap();
328 assert_eq!(f(0.5), 25.0);
329 assert!(
330 EasingFunctions::build_function_using_key_frames(vec![KeyFrame::new(0.0, 0.0)])
331 .is_err()
332 );
333 assert!(
334 EasingFunctions::build_function_using_key_frames(vec![
335 KeyFrame::new(0.5, 0.0),
336 KeyFrame::new(0.5, 100.0)
337 ])
338 .is_err()
339 );
340 }
341
342 #[test]
343 fn source_key_frame_triangle_and_delayed_ramp() {
344 let triangle = EasingFunctions::build_function_using_key_frames(vec![
345 KeyFrame::new(0.0, 0.0),
346 KeyFrame::new(0.5, 1.0),
347 KeyFrame::new(1.0, 0.0),
348 ])
349 .unwrap();
350 for (i, expected) in [0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 0.8, 0.6, 0.4, 0.2, 0.0]
351 .into_iter()
352 .enumerate()
353 {
354 assert!((triangle(i as f64 / 10.0) - expected).abs() < 1e-9);
355 }
356 let ramp = EasingFunctions::build_function_using_key_frames(vec![
357 KeyFrame::new(0.0, 0.0),
358 KeyFrame::new(0.3, 0.0),
359 KeyFrame::new(0.8, 1.0),
360 KeyFrame::new(1.0, 1.0),
361 ])
362 .unwrap();
363 for (i, expected) in [0.0, 0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 1.0]
364 .into_iter()
365 .enumerate()
366 {
367 assert!((ramp(i as f64 / 10.0) - expected).abs() < 1e-9);
368 }
369 }
370}