zng_var/animation/
easing.rs

1//! Common easing functions.
2
3use std::{
4    f32::consts::{FRAC_PI_2, TAU},
5    fmt, ops,
6};
7
8use crate::impl_from_and_into_var;
9
10use super::*;
11
12/// Easing function output.
13///
14/// Usually in the [0..=1] range, but can overshoot. An easing function converts a [`EasingTime`]
15/// into this factor.
16///
17/// # Examples
18///
19/// ```
20/// use zng_unit::*;
21/// use zng_var::animation::easing::{EasingStep, EasingTime};
22///
23/// /// Cubic animation curve.
24/// fn cubic(time: EasingTime) -> EasingStep {
25///     let f = time.fct();
26///     f * f * f
27/// }
28/// ```
29///
30/// Note that all the common easing functions are implemented in [`easing`].
31pub type EasingStep = Factor;
32
33/// Easing function input.
34///
35/// An easing function converts this time into a [`EasingStep`] factor.
36///
37/// The time is always in the [0..=1] range, factors are clamped to this range on creation.
38#[derive(Debug, PartialEq, Copy, Clone, Hash, PartialOrd)]
39pub struct EasingTime(Factor);
40impl_from_and_into_var! {
41    fn from(factor: Factor) -> EasingTime {
42        EasingTime::new(factor)
43    }
44}
45impl EasingTime {
46    /// New from [`Factor`].
47    ///
48    /// The `factor` is clamped to the [0..=1] range.
49    ///
50    /// [`Factor`]: zng_unit::Factor
51    pub fn new(factor: Factor) -> Self {
52        EasingTime(factor.clamp_range())
53    }
54
55    /// New easing time from total `duration`, `elapsed` time and `time_scale`.
56    ///
57    /// If `elapsed >= duration` the time is 1.
58    pub fn elapsed(duration: Duration, elapsed: Duration, time_scale: Factor) -> Self {
59        EasingTime::new(elapsed.as_secs_f32().fct() / duration.as_secs_f32().fct() * time_scale)
60    }
61
62    /// Gets the start time, zero.
63    pub fn start() -> Self {
64        EasingTime(0.fct())
65    }
66
67    /// Gets the end time, one.
68    pub fn end() -> Self {
69        EasingTime(1.fct())
70    }
71
72    /// If the time represents the start of the animation.
73    pub fn is_start(self) -> bool {
74        self == Self::start()
75    }
76
77    /// If the time represents the end of the animation.
78    pub fn is_end(self) -> bool {
79        self == Self::end()
80    }
81
82    /// Get the time as a [`Factor`].
83    ///
84    /// [`Factor`]: zng_unit::Factor
85    pub fn fct(self) -> Factor {
86        self.0
87    }
88
89    /// Get the time as a [`FactorPercent`].
90    ///
91    /// [`FactorPercent`]: zng_unit::FactorPercent
92    pub fn pct(self) -> FactorPercent {
93        self.0.0.pct()
94    }
95
96    /// Flip the time.
97    ///
98    /// Returns `1 - self`.
99    pub fn reverse(self) -> Self {
100        EasingTime(self.0.flip())
101    }
102}
103impl ops::Add for EasingTime {
104    type Output = Self;
105
106    fn add(self, rhs: Self) -> Self::Output {
107        Self(self.0 + rhs.0)
108    }
109}
110impl ops::AddAssign for EasingTime {
111    fn add_assign(&mut self, rhs: Self) {
112        self.0 += rhs.0;
113    }
114}
115impl ops::Sub for EasingTime {
116    type Output = Self;
117
118    fn sub(self, rhs: Self) -> Self::Output {
119        Self(self.0 - rhs.0)
120    }
121}
122impl ops::SubAssign for EasingTime {
123    fn sub_assign(&mut self, rhs: Self) {
124        self.0 -= rhs.0;
125    }
126}
127
128/// Easing functions as a value.
129#[derive(Clone)]
130pub enum EasingFn {
131    /// [`easing::linear`].
132    Linear,
133    /// [`easing::sine`].
134    Sine,
135    /// [`easing::quad`].
136    Quad,
137    /// [`easing::cubic`].
138    Cubic,
139    /// [`easing::quart`].
140    Quart,
141    /// [`easing::quint`].
142    Quint,
143    /// [`easing::expo`].
144    Expo,
145    /// [`easing::circ`].
146    Circ,
147    /// [`easing::back`].
148    Back,
149    /// [`easing::elastic`].
150    Elastic,
151    /// [`easing::bounce`].
152    Bounce,
153    /// [`easing::none`].
154    None,
155    ///Custom function.
156    Custom(Arc<dyn Fn(EasingTime) -> EasingStep + Send + Sync>),
157}
158impl PartialEq for EasingFn {
159    fn eq(&self, other: &Self) -> bool {
160        match (self, other) {
161            (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
162            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
163        }
164    }
165}
166impl Eq for EasingFn {}
167impl fmt::Debug for EasingFn {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        match self {
170            Self::Linear => write!(f, "linear"),
171            Self::Sine => write!(f, "sine"),
172            Self::Quad => write!(f, "quad"),
173            Self::Cubic => write!(f, "cubic"),
174            Self::Quart => write!(f, "quart"),
175            Self::Quint => write!(f, "quint"),
176            Self::Expo => write!(f, "expo"),
177            Self::Circ => write!(f, "circ"),
178            Self::Back => write!(f, "back"),
179            Self::Elastic => write!(f, "elastic"),
180            Self::Bounce => write!(f, "bounce"),
181            Self::None => write!(f, "none"),
182            Self::Custom(_) => f.debug_tuple("Custom").finish(),
183        }
184    }
185}
186impl EasingFn {
187    /// Create a closure that calls the easing function.
188    pub fn ease_fn(&self) -> impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static {
189        let me = self.clone();
190        move |t| me(t)
191    }
192
193    /// New custom function.
194    pub fn custom(f: impl Fn(EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
195        Self::Custom(Arc::new(f))
196    }
197
198    /// Creates a custom function that is `self` modified by `modifier`
199    pub fn modified(self, modifier: impl Fn(&dyn Fn(EasingTime) -> EasingStep, EasingTime) -> EasingStep + Send + Sync + 'static) -> Self {
200        Self::custom(move |t| modifier(&*self, t))
201    }
202
203    /// Creates a custom function that is `self` modified by [`easing::ease_out`].
204    pub fn ease_out(self) -> Self {
205        self.modified(|f, t| easing::ease_out(f, t))
206    }
207
208    /// Creates a custom function that is `self` modified by [`easing::ease_in_out`].
209    pub fn ease_in_out(self) -> Self {
210        self.modified(|f, t| easing::ease_in_out(f, t))
211    }
212
213    /// Creates a custom function that is `self` modified by [`easing::ease_out_in`].
214    pub fn ease_out_in(self) -> Self {
215        self.modified(|f, t| easing::ease_out_in(f, t))
216    }
217
218    /// Creates a custom function that is `self` modified by [`easing::reverse`].
219    pub fn reverse(self) -> Self {
220        self.modified(|f, t| easing::reverse(f, t))
221    }
222
223    /// Creates a custom function that is `self` modified by [`easing::reverse_out`].
224    pub fn reverse_out(self) -> Self {
225        self.modified(|f, t| easing::reverse_out(f, t))
226    }
227}
228impl ops::Deref for EasingFn {
229    type Target = dyn Fn(EasingTime) -> EasingStep + Send + Sync;
230
231    fn deref(&self) -> &Self::Target {
232        match self {
233            EasingFn::Linear => &easing::linear,
234            EasingFn::Sine => &easing::sine,
235            EasingFn::Quad => &easing::quad,
236            EasingFn::Cubic => &easing::cubic,
237            EasingFn::Quart => &easing::quad,
238            EasingFn::Quint => &easing::quint,
239            EasingFn::Expo => &easing::expo,
240            EasingFn::Circ => &easing::circ,
241            EasingFn::Back => &easing::back,
242            EasingFn::Elastic => &easing::elastic,
243            EasingFn::Bounce => &easing::bounce,
244            EasingFn::None => &easing::none,
245            EasingFn::Custom(c) => &**c,
246        }
247    }
248}
249
250/// Simple linear transition, no easing, no acceleration.
251pub fn linear(time: EasingTime) -> EasingStep {
252    time.fct()
253}
254
255/// Quadratic transition (t²).
256pub fn quad(time: EasingTime) -> EasingStep {
257    let f = time.fct();
258    f * f
259}
260
261/// Cubic transition (t³).
262pub fn cubic(time: EasingTime) -> EasingStep {
263    let f = time.fct();
264    f * f * f
265}
266
267/// Fourth power transition (t⁴).
268pub fn quart(time: EasingTime) -> EasingStep {
269    let f = time.fct();
270    f * f * f * f
271}
272
273/// Fifth power transition (t⁵).
274pub fn quint(time: EasingTime) -> EasingStep {
275    let f = time.fct();
276    f * f * f * f * f
277}
278
279/// Sine transition. Slow start, fast end.
280pub fn sine(time: EasingTime) -> EasingStep {
281    let f = time.fct().0;
282    (1.0 - (f * FRAC_PI_2).cos()).fct()
283}
284
285/// Exponential transition. Very slow start, very fast end.
286pub fn expo(time: EasingTime) -> EasingStep {
287    let f = time.fct();
288    if f == 0.fct() {
289        0.fct()
290    } else {
291        2.0_f32.powf(10.0 * f.0 - 10.0).fct()
292    }
293}
294
295/// Cubic transition with slightly slowed start then [`cubic`].
296pub fn circ(time: EasingTime) -> EasingStep {
297    let f = time.fct().0;
298    (1.0 - (1.0 - f.powf(2.0)).sqrt()).fct()
299}
300
301/// Cubic transition that goes slightly negative to start and ends very fast.
302///
303/// Like it backs-up and the shoots out.
304pub fn back(time: EasingTime) -> EasingStep {
305    let f = time.fct().0;
306    (f * f * (2.70158 * f - 1.70158)).fct()
307}
308
309/// Oscillating transition that grows in magnitude, goes negative twice.
310pub fn elastic(time: EasingTime) -> EasingStep {
311    let t = time.fct();
312
313    const C: f32 = TAU / 3.0;
314
315    if t == 0.fct() || t == 1.fct() {
316        t
317    } else {
318        let t = t.0;
319        let s = -(2.0_f32.powf(10.0 * t - 10.0)) * ((t * 10.0 - 10.75) * C).sin();
320        s.fct()
321    }
322}
323
324/// Oscillating transition that grows in magnitude, does not go negative, when the curve
325/// is about to go negative it sharply transitions to a new arc of larger magnitude.
326pub fn bounce(time: EasingTime) -> EasingStep {
327    const N: f32 = 7.5625;
328    const D: f32 = 2.75;
329
330    let mut t = 1.0 - time.fct().0;
331
332    let f = if t < 1.0 / D {
333        N * t * t
334    } else if t < 2.0 / D {
335        t -= 1.5 / D;
336        N * t * t + 0.75
337    } else if t < 2.5 / D {
338        t -= 2.25 / D;
339        N * t * t + 0.9375
340    } else {
341        t -= 2.625 / D;
342        N * t * t + 0.984375
343    };
344
345    (1.0 - f).fct()
346}
347
348/// X coordinate is time, Y coordinate is function advancement.
349/// The nominal range for both is 0 to 1.
350///
351/// The start and end points are always (0, 0) and (1, 1) so that a transition or animation
352/// starts at 0% and ends at 100%.
353pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32, time: EasingTime) -> EasingStep {
354    let f = time.fct().0 as f64;
355    (Bezier::new(x1, y1, x2, y2).solve(f, 0.00001) as f32).fct()
356}
357
358/// Jumps to the final value by a number of `steps`.
359///
360/// Starts from the first step value immediately.
361pub fn step_ceil(steps: u32, time: EasingTime) -> EasingStep {
362    let steps = steps as f32;
363    let step = (steps * time.fct().0).ceil();
364    (step / steps).fct()
365}
366
367/// Jumps to the final value by a number of `steps`.
368///
369/// Waits until first step to output the first step value.
370pub fn step_floor(steps: u32, time: EasingTime) -> EasingStep {
371    let steps = steps as f32;
372    let step = (steps * time.fct().0).floor();
373    (step / steps).fct()
374}
375
376/// Always `1.fct()`, that is, the completed transition.
377pub fn none(_: EasingTime) -> EasingStep {
378    1.fct()
379}
380
381/// Applies the `ease_fn`.
382pub fn ease_in(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
383    ease_fn(time)
384}
385
386/// Applies the `ease_fn` in reverse and flipped.
387pub fn ease_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
388    ease_fn(time.reverse()).flip()
389}
390
391/// Applies [`ease_in`] for the first half then [`ease_out`] scaled to fit a single duration (1.0).
392pub fn ease_in_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
393    let t = time.fct();
394    if t <= 0.5.fct() {
395        ease_in(&ease_fn, EasingTime::new(t * 2.fct())) / 2.fct()
396    } else {
397        ease_out(ease_fn, EasingTime::new((t - 0.5.fct()) * 2.fct())) / 2.fct() + 0.5.fct()
398    }
399}
400
401/// Applies [`ease_out`] for the first half then [`ease_in`] scaled to fit a single duration (1.0).
402pub fn ease_out_in(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
403    let t = time.fct();
404    if t <= 0.5.fct() {
405        ease_out(&ease_fn, EasingTime::new(t * 2.fct())) / 2.fct()
406    } else {
407        ease_in(ease_fn, EasingTime::new((t - 0.5.fct()) * 2.fct())) / 2.fct() + 0.5.fct()
408    }
409}
410
411/// Applies the `ease_fn` in reverse.
412pub fn reverse(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
413    ease_fn(time.reverse())
414}
415
416/// Applies the `ease_fn` flipped.
417pub fn reverse_out(ease_fn: impl Fn(EasingTime) -> EasingStep, time: EasingTime) -> EasingStep {
418    ease_fn(time).flip()
419}
420
421pub use bezier::*;
422use zng_unit::{FactorPercent, FactorUnits as _};
423
424mod bezier {
425    /* This Source Code Form is subject to the terms of the Mozilla Public
426     * License, v. 2.0. If a copy of the MPL was not distributed with this
427     * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
428
429    const NEWTON_METHOD_ITERATIONS: u8 = 8;
430
431    /// A unit cubic Bézier curve, used for timing functions in CSS transitions and animations.
432    pub struct Bezier {
433        ax: f64,
434        bx: f64,
435        cx: f64,
436        ay: f64,
437        by: f64,
438        cy: f64,
439    }
440
441    impl Bezier {
442        /// Create a unit cubic Bézier curve from the two middle control points.
443        ///
444        /// X coordinate is time, Y coordinate is function advancement.
445        /// The nominal range for both is 0 to 1.
446        ///
447        /// The start and end points are always (0, 0) and (1, 1) so that a transition or animation
448        /// starts at 0% and ends at 100%.
449        pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Bezier {
450            let cx = 3. * x1 as f64;
451            let bx = 3. * (x2 as f64 - x1 as f64) - cx;
452
453            let cy = 3. * y1 as f64;
454            let by = 3. * (y2 as f64 - y1 as f64) - cy;
455
456            Bezier {
457                ax: 1.0 - cx - bx,
458                bx,
459                cx,
460                ay: 1.0 - cy - by,
461                by,
462                cy,
463            }
464        }
465
466        fn sample_curve_x(&self, t: f64) -> f64 {
467            // ax * t^3 + bx * t^2 + cx * t
468            ((self.ax * t + self.bx) * t + self.cx) * t
469        }
470
471        fn sample_curve_y(&self, t: f64) -> f64 {
472            ((self.ay * t + self.by) * t + self.cy) * t
473        }
474
475        fn sample_curve_derivative_x(&self, t: f64) -> f64 {
476            (3.0 * self.ax * t + 2.0 * self.bx) * t + self.cx
477        }
478
479        fn solve_curve_x(&self, x: f64, epsilon: f64) -> f64 {
480            // Fast path: Use Newton's method.
481            let mut t = x;
482            for _ in 0..NEWTON_METHOD_ITERATIONS {
483                let x2 = self.sample_curve_x(t);
484                if x2.approx_eq(x, epsilon) {
485                    return t;
486                }
487                let dx = self.sample_curve_derivative_x(t);
488                if dx.approx_eq(0.0, 1e-6) {
489                    break;
490                }
491                t -= (x2 - x) / dx;
492            }
493
494            // Slow path: Use bisection.
495            let (mut lo, mut hi, mut t) = (0.0, 1.0, x);
496
497            if t < lo {
498                return lo;
499            }
500            if t > hi {
501                return hi;
502            }
503
504            while lo < hi {
505                let x2 = self.sample_curve_x(t);
506                if x2.approx_eq(x, epsilon) {
507                    return t;
508                }
509                if x > x2 {
510                    lo = t
511                } else {
512                    hi = t
513                }
514                t = (hi - lo) / 2.0 + lo
515            }
516
517            t
518        }
519
520        /// Solve the bezier curve for a given `x` and an `epsilon`, that should be
521        /// between zero and one.
522        pub fn solve(&self, x: f64, epsilon: f64) -> f64 {
523            self.sample_curve_y(self.solve_curve_x(x, epsilon))
524        }
525    }
526
527    trait ApproxEq {
528        fn approx_eq(self, value: Self, epsilon: Self) -> bool;
529    }
530
531    impl ApproxEq for f64 {
532        fn approx_eq(self, value: f64, epsilon: f64) -> bool {
533            (self - value).abs() < epsilon
534        }
535    }
536}