1use core::f32::consts::PI;
4
5use crate::{Easing, EasingFunction, EasingKind};
6
7macro_rules! declare_easing_function {
8 ($name:ident, $($anchor_name:ident)?, $description:literal, $closure:expr) => {
9 #[doc = $description]
11 $(#[doc = concat!("\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")])?
12 #[derive(Clone, Copy, Debug)]
13 pub struct $name;
14
15 impl $name {
16 #[doc = $description]
18 $(#[doc = concat!("\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")])?
19 #[must_use]
20 pub fn ease(progress: f32) -> f32 {
21 let closure = force_closure_type($closure);
22 closure(progress)
23 }
24 }
25
26 impl Easing for $name {
27 fn ease(&self, progress: f32) -> f32 {
28 Self::ease(progress)
29 }
30 }
31
32 impl From<$name> for EasingFunction {
33 fn from(_function: $name) -> Self {
34 Self::from_fn($name::ease)
35 }
36 }
37 };
38}
39
40macro_rules! declare_easing_functions {
41 ($(($name:ident, $([$anchor_name:ident],)? $name_no_ease:ident, $description:literal, $closure:expr)),+) => {
42 #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
44 #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45 pub enum StandardEasing {
46 $(
47 #[doc = $description]
49 $(#[doc = concat!("\n\nSee <https://easings.net/#", stringify!($anchor_name), "> for a visualization and more information.")])?
50 $name_no_ease,
51 )+
52 }
53
54 impl StandardEasing {
55 #[must_use]
57 pub fn all() -> &'static [StandardEasing] {
58 static ALL: [StandardEasing; 31] = [
59 $(StandardEasing::$name_no_ease),+
60 ];
61 &ALL
62 }
63 }
64
65 impl From<StandardEasing> for EasingFunction {
66 fn from(easing: StandardEasing) -> Self {
67 match easing {
68 $(StandardEasing::$name_no_ease => Self::from($name)),+
69 }
70 }
71 }
72
73 impl TryFrom<EasingFunction> for StandardEasing {
74 type Error = NonStandardEasing;
75
76 fn try_from(func: EasingFunction) -> Result<Self, Self::Error> {
77 let EasingKind::Fn(easing_fn) = &func.0 else {
78 return Err(NonStandardEasing(func))
79 };
80 let easing_fn = *easing_fn as *const fn(f32) -> f32;
81
82 if false {
83 unreachable!()
84 } $(else
85 if easing_fn == $name::ease as *const fn(f32) -> f32 {
86 Ok(Self::$name_no_ease)
87 })+ else {
88 Err(NonStandardEasing(func))
89 }
90 }
91 }
92
93 impl Easing for StandardEasing {
94 fn ease(&self, percent: f32) -> f32 {
95 match self {
96 $(Self::$name_no_ease => $name::ease(percent)),+
97 }
98 }
99 }
100
101 $(
102 declare_easing_function!($name, $($anchor_name)?, $description, $closure);
103 )+
104 };
105}
106
107impl Default for StandardEasing {
108 fn default() -> Self {
109 Self::Linear
110 }
111}
112
113#[derive(Debug, Clone, PartialEq)]
116pub struct NonStandardEasing(pub EasingFunction);
117
118fn force_closure_type(f: impl Fn(f32) -> f32) -> impl Fn(f32) -> f32 {
120 f
121}
122
123declare_easing_functions!(
124 (
125 EaseInSine,
126 [easeInSine],
127 InSine,
128 "in using a sine wave",
129 |percent| 1. - (percent * PI / 2.).cos()
130 ),
131 (
132 EaseOutSine,
133 [easeOutSine],
134 OutSine,
135 "out using a sine wave",
136 |percent| (percent * PI / 2.).sin()
137 ),
138 (
139 EaseInOutSine,
140 [easeInOutSine],
141 InOutSine,
142 "in and out using a sine wave",
143 |percent| -((percent * PI).cos() - 1.) / 2.
144 ),
145 (
146 EaseInQuadradic,
147 [easeInQuad],
148 InQuadradic,
149 "in using a quadradic (x^2) curve",
150 squared
151 ),
152 (
153 EaseOutQuadradic,
154 [easeOutQuad],
155 OutQuadradic,
156 "out using a quadradic (x^2) curve",
157 |percent| 1. - squared(1. - percent)
158 ),
159 (
160 EaseInOutQuadradic,
161 [easeInOutQuad],
162 InOutQuadradic,
163 "in and out using a quadradic (x^2) curve",
164 |percent| {
165 if percent < 0.5 {
166 2. * percent * percent
167 } else {
168 1. - squared(-2. * percent + 2.) / 2.
169 }
170 }
171 ),
172 (
173 EaseInCubic,
174 [easeInCubic],
175 InCubic,
176 "in using a cubic (x^3) curve",
177 cubed
178 ),
179 (
180 EaseOutCubic,
181 [easeOutCubic],
182 OutCubic,
183 "out using a cubic (x^3) curve",
184 |percent| 1. - cubed(1. - percent)
185 ),
186 (
187 EaseInOutCubic,
188 [easeInOutCubic],
189 InOutCubic,
190 "in and out using a cubic (x^3) curve",
191 |percent| {
192 if percent < 0.5 {
193 4. * cubed(percent)
194 } else {
195 1. - cubed(-2. * percent + 2.) / 2.
196 }
197 }
198 ),
199 (
200 EaseInQuartic,
201 [easeInQuart],
202 InQuartic,
203 "in using a quartic (x^4) curve",
204 quarted
205 ),
206 (
207 EaseOutQuartic,
208 [easeOutQuart],
209 OutQuartic,
210 "out using a quartic (x^4) curve",
211 |percent| 1. - quarted(1. - percent)
212 ),
213 (
214 EaseInOutQuartic,
215 [easeInOutQuart],
216 InOutQuartic,
217 "in and out using a quartic (x^4) curve",
218 |percent| {
219 if percent < 0.5 {
220 8. * quarted(percent)
221 } else {
222 1. - quarted(-2. * percent + 2.) / 2.
223 }
224 }
225 ),
226 (
227 EaseInQuintic,
228 [easeInQuint],
229 InQuintic,
230 "in using a quintic (x^5) curve",
231 quinted
232 ),
233 (
234 EaseOutQuintic,
235 [easeOutQuint],
236 OutQuintic,
237 "out using a quintic (x^5) curve",
238 |percent| 1. - quinted(1. - percent)
239 ),
240 (
241 EaseInOutQuintic,
242 [easeInOutQuint],
243 InOutQuintic,
244 "in and out using a quintic (x^5) curve",
245 |percent| {
246 if percent < 0.5 {
247 16. * quinted(percent)
248 } else {
249 1. - quinted(-2. * percent + 2.) / 2.
250 }
251 }
252 ),
253 (
254 EaseInExponential,
255 [easeInExpo],
256 InExponential,
257 "in using an expenential curve",
258 |percent| { 2f32.powf(10. * percent - 10.) }
259 ),
260 (
261 EaseOutExponential,
262 [easeOutExpo],
263 OutExponential,
264 "out using an expenential curve",
265 |percent| { 1. - 2f32.powf(-10. * percent) }
266 ),
267 (
268 EaseInOutExponential,
269 [easeInOutExpo],
270 InOutExponential,
271 "in and out using an expenential curve",
272 |percent| if percent < 0.5 {
273 2f32.powf(20. * percent - 10.) / 2.
274 } else {
275 (2. - 2f32.powf(-20. * percent + 10.)) / 2.
276 }
277 ),
278 (
279 EaseInCircular,
280 [easeInCirc],
281 InCircular,
282 "in using a curve resembling the top-left arc of a circle",
283 |percent| 1. - (1. - squared(percent)).sqrt()
284 ),
285 (
286 EaseOutCircular,
287 [easeOutCirc],
288 OutCircular,
289 "out using a curve resembling the top-left arc of a circle",
290 |percent| (1. - squared(percent - 1.)).sqrt()
291 ),
292 (
293 EaseInOutCircular,
294 [easeInOutCirc],
295 InOutCircular,
296 "in and out using a curve resembling the top-left arc of a circle",
297 |percent| {
298 if percent < 0.5 {
299 (1. - (1. - squared(2. * percent)).sqrt()) / 2.
300 } else {
301 ((1. - squared(-2. * percent + 2.)).sqrt() + 1.) / 2.
302 }
303 }
304 ),
305 (
306 EaseInBack,
307 [easeInBack],
308 InBack,
309 "in using a curve that backs away initially",
310 |percent| {
311 let squared = squared(percent);
312 let cubed = squared * percent;
313 C3 * cubed - C1 * squared
314 }
315 ),
316 (
317 EaseOutBack,
318 [easeOutBack],
319 OutBack,
320 "out using a curve that backs away initially",
321 |percent| {
322 let percent_minus_one = percent - 1.;
323 let squared = squared(percent_minus_one);
324 let cubed = squared * percent_minus_one;
325 1. + C3 * cubed + C1 * squared
326 }
327 ),
328 (
329 EaseInOutBack,
330 [easeInOutBack],
331 InOutBack,
332 "in and out using a curve that backs away initially",
333 |percent| {
334 if percent < 0.5 {
335 (squared(2. * percent) * ((C2 + 1.) * 2. * percent - C2)) / 2.
336 } else {
337 (squared(2. * percent - 2.) * ((C2 + 1.) * (percent * 2. - 2.) + C2) + 2.) / 2.
338 }
339 }
340 ),
341 (
342 EaseInElastic,
343 [easeInElastic],
344 InElastic,
345 "in using a curve that bounces around the start initially then quickly accelerates",
346 |percent| { -(2f32.powf(10. * percent - 10.)) * ((percent * 10. - 10.75) * C4).sin() }
347 ),
348 (
349 EaseOutElastic,
350 [easeOutElastic],
351 OutElastic,
352 "out using a curve that bounces around the start initially then quickly accelerates",
353 |percent| { 2f32.powf(-10. * percent) * ((percent * 10. - 0.75) * C4).sin() + 1. }
354 ),
355 (
356 EaseInOutElastic,
357 [easeInOutElastic],
358 InOutElastic,
359 "in and out using a curve that bounces around the start initially then quickly accelerates",
360 |percent| if percent < 0.5 {
361 -(2f32.powf(20. * percent - 10.)) * ((percent * 20. - 11.125) * C5).sin() / 2.
362 } else {
363 2f32.powf(-20. * percent + 10.) * ((percent * 20. - 11.125) * C5).sin() / 2. + 1.
364 }
365 ),
366 (
367 EaseInBounce,
368 [easeInBounce],
369 InBounce,
370 "in using a curve that bounces progressively closer as it progresses",
371 |percent| 1. - EaseOutBounce.ease(1. - percent)
372 ),
373 (
374 EaseOutBounce,
375 [easeOutBounce],
376 OutBounce,
377 "out using a curve that bounces progressively closer as it progresses",
378 |percent| {
379 const N1: f32 = 7.5625;
380 const D1: f32 = 2.75;
381
382 if percent < 1. / D1 {
383 N1 * squared(percent)
384 } else if percent < 2. / D1 {
385 let percent = percent - 1.5 / D1;
386 N1 * squared(percent) + 0.75
387 } else if percent < 2.5 / D1 {
388 let percent = percent - 2.25 / D1;
389 N1 * squared(percent) + 0.9375
390 } else {
391 let percent = percent - 2.625 / D1;
392 N1 * squared(percent) + 0.984_375
393 }
394 }
395 ),
396 (
397 EaseInOutBounce,
398 [easeInOutBounce],
399 InOutBounce,
400 "in and out using a curve that bounces progressively closer as it progresses",
401 |percent| {
402 if percent < 0.5 {
403 (1. - EaseOutBounce::ease(1. - 2. * percent)) / 2.
404 } else {
405 (1. + EaseOutBounce::ease(2. * percent - 1.)) / 2.
406 }
407 }
408 ),
409 (Linear, Linear, "linearly", |percent| percent)
410);
411
412fn squared(value: f32) -> f32 {
413 value * value
414}
415
416fn cubed(value: f32) -> f32 {
417 value * value * value
418}
419
420fn quarted(value: f32) -> f32 {
421 let sq = squared(value);
422 squared(sq)
423}
424
425fn quinted(value: f32) -> f32 {
426 let squared = squared(value);
427 let cubed = squared * value;
428 squared * cubed
429}
430
431const C1: f32 = 1.70158;
432const C2: f32 = C1 * 1.525;
433const C3: f32 = C1 + 1.;
434const C4: f32 = (2. * PI) / 3.;
435const C5: f32 = (2. * PI) / 4.5;
436
437#[test]
438fn roundtrip() {
439 for &easing in StandardEasing::all() {
440 let f = EasingFunction::from(dbg!(easing));
441 let rt = StandardEasing::try_from(f);
442 assert_eq!(rt, Ok(easing));
443 }
444}