mina_core/
easing.rs

1//! Contains the [`Easing`] enum which defines many standard easing types available for animations,
2//! as well as an [`EasingFunction`] trait for defining custom easings.
3
4use dyn_clone::{clone_trait_object, DynClone};
5use lazy_static::lazy_static;
6use lyon_geom::{CubicBezierSegment, Point};
7use std::fmt::Debug;
8
9/// Provides an easing function, AKA animation timing function, for non-linear interpolation of
10/// values, typically along some curve.
11///
12/// Easing functions and [`Lerp`](crate::interpolation::Lerp) are complementary. `Lerp` is always
13/// responsible for determining the value of a given animation property at a given time `t` (or `x`
14/// in lerp terminology), but `EasingFunction` can modify which `x` value the lerp will use in its
15/// evaluation. This has the same effect as using the easing function directly, because linear
16/// interpolation constitutes an identity function over normalized `y`.
17pub trait EasingFunction: Debug + DynClone + Send + Sync {
18    /// Computes the `y` value along the curve for a given `x` position.
19    ///
20    /// Expects `x` to be normalized (from 0 to 1) and returns a normalized y-value which is
21    /// typically between 0 and 1, but may be outside that range (e.g. [Easing::OutBack]).
22    fn calc(&self, x: f32) -> f32;
23}
24
25clone_trait_object!(EasingFunction);
26
27/// Specifies a standard or custom [`EasingFunction`].
28///
29/// Available easings include:
30/// - CSS standard: `Ease`, `In`, `Out`, `InOut` corresponding to `ease`, `ease-in`, `ease-out` and
31///   `ease-in-out`
32/// - Common easings that can be implemented with a cubic bezier function, i.e. the majority of
33///   functions listed on <https://easings.net> except for the "elastic" and "bounce" types.
34/// - User-defined functions via [`Custom`](Easing::Custom).
35#[derive(Clone, Debug, Default)]
36pub enum Easing {
37    /// Linear easing, i.e. no easing or curve, only straight-line interpolation.
38    #[default]
39    Linear,
40    /// Curve equivalent to CSS
41    /// [`ease`](https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#ease).
42    Ease,
43    /// Curve equivalent to CSS
44    /// [`ease-in`](https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#ease-in).
45    In,
46    /// Curve equivalent to CSS
47    /// [`ease-out`](https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#ease-out).
48    Out,
49    /// Curve equivalent to CSS
50    /// [`ease-in-out`](https://developer.mozilla.org/en-US/docs/Web/CSS/easing-function#ease-in-out).
51    InOut,
52    /// Sinusoidal easing that starts slowly and ends quickly. Subtle, almost linear curve.
53    ///
54    /// See: <https://easings.net/#easeInSine>
55    InSine,
56    /// Sinusoidal easing that starts quickly and ends slowly. Subtle, almost linear curve.
57    ///
58    /// See: <https://easings.net/#easeOutSine>
59    OutSine,
60    /// Sinusoidal easing that starts slowly, speeds up, and then ends slowly. Subtle, almost linear
61    /// curve.
62    ///
63    /// See: <https://easings.net/#easeInOutSine>
64    InOutSine,
65    /// Quadratic (`^2`) easing that starts slowly and ends quickly. Slightly steeper curve than
66    /// [`InSine`](Self::InSine).
67    ///
68    /// See: <https://easings.net/#easeInQuad>
69    InQuad,
70    /// Quadratic (`^2`) easing that starts quickly and ends slowly. Slightly steeper curve than
71    /// [`OutSine`](Self::OutSine).
72    ///
73    /// See: <https://easings.net/#easeOutQuad>
74    OutQuad,
75    /// Quadratic (`^2`) easing that starts slowly, speeds up, and then ends slowly. Slightly
76    /// steeper curve than [`InOutSine`](Self::InOutSine).
77    ///
78    /// See: <https://easings.net/#easeInOutQuad>
79    InOutQuad,
80    /// Cubic (`^3`) easing that starts slowly and ends quickly. Slightly steeper curve than
81    /// [`InQuad`](Self::InQuad).
82    ///
83    /// See: <https://easings.net/#easeInCubic>
84    InCubic,
85    /// Cubic (`^3`) easing that starts quickly and ends slowly. Slightly steeper curve than
86    /// [`OutQuad`](Self::OutQuad).
87    ///
88    /// See: <https://easings.net/#easeOutCubic>
89    OutCubic,
90    /// Cubic (`^3`) easing that starts slowly, speeds up, and then ends slowly. Slightly steeper
91    /// curve than [`InOutQuad`](Self::InOutQuad).
92    ///
93    /// See: <https://easings.net/#easeInOutCubic>
94    InOutCubic,
95    /// Quartic (`^4`) easing that starts slowly and ends quickly. Slightly steeper curve than
96    /// [`InCubic`](Self::InCubic).
97    ///
98    /// See: <https://easings.net/#easeInQuart>
99    InQuart,
100    /// Quartic (`^4`) easing that starts quickly and ends slowly. Slightly steeper curve than
101    /// [`OutCubic`](Self::OutCubic).
102    ///
103    /// See: <https://easings.net/#easeOutQuart>
104    OutQuart,
105    /// Quartic (`^4`) easing that starts slowly, speeds up, and then ends slowly. Slightly steeper
106    /// curve than [`InOutCubic`](Self::InOutCubic).
107    ///
108    /// See: <https://easings.net/#easeInOutQuart>
109    InOutQuart,
110    /// Quintic (`^5`) easing that starts slowly and ends quickly. Slightly steeper curve than
111    /// [`InQuart`](Self::InQuart).
112    ///
113    /// See: <https://easings.net/#easeInQuint>
114    InQuint,
115    /// Quintic (`^5`) easing that starts quickly and ends slowly. Slightly steeper curve than
116    /// [`OutQuart`](Self::OutQuart).
117    ///
118    /// See: <https://easings.net/#easeOutQuint>
119    OutQuint,
120    /// Quintic (`^5`) easing that starts slowly, speeds up, and then ends slowly. Slightly steeper
121    /// curve than [`InOutQuart`](Self::InOutQuart).
122    ///
123    /// See: <https://easings.net/#easeInOutQuint>
124    InOutQuint,
125    /// Exponential easing that starts slowly and ends quickly. Steeper curve than
126    /// [`InQuint`](Self::InQuint) and generally only suitable for long animations/frames.
127    ///
128    /// See: <https://easings.net/#easeInExpo>
129    InExpo,
130    /// Exponential easing that starts quickly and ends slowly. Steeper curve than
131    /// [`OutQuint`](Self::OutQuint) and generally only suitable for long animations/frames.
132    ///
133    /// See: <https://easings.net/#easeOutExpo>
134    OutExpo,
135    /// Exponential easing that starts slowly, speeds up, and then ends slowly. Steeper curve than
136    /// [`InOutQuint`](Self::InOutQuint) and generally only suitable for long animations/frames.
137    ///
138    /// See: <https://easings.net/#easeInOutExpo>
139    InOutExpo,
140    /// A curve that looks like the lower-right quarter of a circle. Starts slowly, and speeds up
141    /// dramatically. Generally only suitable for long animations/frames.
142    ///
143    /// See: <https://easings.net/#easeInCirc>
144    InCirc,
145    /// A curve that looks like the upper-left quarter of a circle. Starts very quickly, and
146    /// decelerates dramatically. Generally only suitable for long animations/frames.
147    ///
148    /// See: <https://easings.net/#easeOutCirc>
149    OutCirc,
150    /// A curve that looks like the lower-right quarter of a circle connected to the upper-left
151    /// quarter of a circle. Starts very slowly, accelerates dramatically, and ends very slowly.
152    /// dramatically. Generally only suitable for long animations/frames.
153    ///
154    /// See: <https://easings.net/#easeInOutCirc>
155    InOutCirc,
156    /// A curve that has similar timing to [InExpo](Easing::InExpo) but moves slightly backward
157    /// (negative) before accelerating forward.
158    ///
159    /// See: <https://easings.net/#easeInBack>
160    InBack,
161    /// A curve that has similar timing to [OutExpo](Easing::OutExpo) but overshoots the terminal
162    /// value (i.e. goes above 1.0) before decelerating backward and settling at the final value.
163    ///
164    /// See: <https://easings.net/#easeOutBack>
165    OutBack,
166    /// A curve that has similar timing to [InOutExpo](Easing::InOutExpo) but moves slightly
167    /// backward (negative) before accelerating forward and also overshoots the terminal value (i.e.
168    /// goes above 1.0) before decelerating backward and settling at the final value.
169    ///
170    /// See: <https://easings.net/#easeInOutBack>
171    InOutBack,
172    /// User-defined easing, such as an ad-hoc [CubicBezierEasing].
173    Custom(Box<dyn EasingFunction>),
174}
175
176impl EasingFunction for Easing {
177    fn calc(&self, x: f32) -> f32 {
178        match self {
179            Self::Linear => EASE_LINEAR.calc(x),
180            Self::Ease => EASE_WEB.calc(x),
181            Self::In => EASE_IN.calc(x),
182            Self::Out => EASE_OUT.calc(x),
183            Self::InOut => EASE_IN_OUT.calc(x),
184            Self::InSine => EASE_IN_SINE.calc(x),
185            Self::OutSine => EASE_OUT_SINE.calc(x),
186            Self::InOutSine => EASE_IN_OUT_SINE.calc(x),
187            Self::InQuad => EASE_IN_QUAD.calc(x),
188            Self::OutQuad => EASE_OUT_QUAD.calc(x),
189            Self::InOutQuad => EASE_IN_OUT_QUAD.calc(x),
190            Self::InCubic => EASE_IN_CUBIC.calc(x),
191            Self::OutCubic => EASE_OUT_CUBIC.calc(x),
192            Self::InOutCubic => EASE_IN_OUT_CUBIC.calc(x),
193            Self::InQuart => EASE_IN_QUART.calc(x),
194            Self::OutQuart => EASE_OUT_QUART.calc(x),
195            Self::InOutQuart => EASE_IN_OUT_QUART.calc(x),
196            Self::InQuint => EASE_IN_QUINT.calc(x),
197            Self::OutQuint => EASE_OUT_QUINT.calc(x),
198            Self::InOutQuint => EASE_IN_OUT_QUINT.calc(x),
199            Self::InExpo => EASE_IN_EXPO.calc(x),
200            Self::OutExpo => EASE_OUT_EXPO.calc(x),
201            Self::InOutExpo => EASE_IN_OUT_EXPO.calc(x),
202            Self::InCirc => EASE_IN_CIRC.calc(x),
203            Self::OutCirc => EASE_OUT_CIRC.calc(x),
204            Self::InOutCirc => EASE_IN_OUT_CIRC.calc(x),
205            Self::InBack => EASE_IN_BACK.calc(x),
206            Self::OutBack => EASE_OUT_BACK.calc(x),
207            Self::InOutBack => EASE_IN_OUT_BACK.calc(x),
208            Self::Custom(custom) => custom.calc(x),
209        }
210    }
211}
212
213lazy_static! {
214    static ref EASE_LINEAR: LinearEasing = LinearEasing;
215    static ref EASE_WEB: CubicBezierEasing = cubic_bezier(0.25, 0.1, 0.25, 1.0);
216    static ref EASE_IN: CubicBezierEasing = cubic_bezier(0.42, 0.0, 1.0, 1.0);
217    static ref EASE_OUT: CubicBezierEasing = cubic_bezier(0.0, 0.0, 0.58, 1.0);
218    static ref EASE_IN_OUT: CubicBezierEasing = cubic_bezier(0.42, 0.0, 0.58, 1.0);
219    static ref EASE_IN_SINE: CubicBezierEasing = cubic_bezier(0.12, 0.0, 0.39, 0.0);
220    static ref EASE_OUT_SINE: CubicBezierEasing = cubic_bezier(0.61, 1.0, 0.88, 1.0);
221    static ref EASE_IN_OUT_SINE: CubicBezierEasing = cubic_bezier(0.37, 0.0, 0.63, 1.0);
222    static ref EASE_IN_QUAD: CubicBezierEasing = cubic_bezier(0.11, 0.0, 0.5, 0.0);
223    static ref EASE_OUT_QUAD: CubicBezierEasing = cubic_bezier(0.5, 1.0, 0.89, 1.0);
224    static ref EASE_IN_OUT_QUAD: CubicBezierEasing = cubic_bezier(0.45, 0.0, 0.55, 1.0);
225    static ref EASE_IN_CUBIC: CubicBezierEasing = cubic_bezier(0.32, 0.0, 0.67, 0.0);
226    static ref EASE_OUT_CUBIC: CubicBezierEasing = cubic_bezier(0.33, 1.0, 0.68, 1.0);
227    static ref EASE_IN_OUT_CUBIC: CubicBezierEasing = cubic_bezier(0.65, 0.0, 0.35, 1.0);
228    static ref EASE_IN_QUART: CubicBezierEasing = cubic_bezier(0.5, 0.0, 0.75, 0.0);
229    static ref EASE_OUT_QUART: CubicBezierEasing = cubic_bezier(0.25, 1.0, 0.5, 1.0);
230    static ref EASE_IN_OUT_QUART: CubicBezierEasing = cubic_bezier(0.76, 0.0, 0.24, 1.0);
231    static ref EASE_IN_QUINT: CubicBezierEasing = cubic_bezier(0.64, 0.0, 0.78, 0.0);
232    static ref EASE_OUT_QUINT: CubicBezierEasing = cubic_bezier(0.22, 1.0, 0.36, 1.0);
233    static ref EASE_IN_OUT_QUINT: CubicBezierEasing = cubic_bezier(0.83, 0.0, 0.17, 1.0);
234    static ref EASE_IN_EXPO: CubicBezierEasing = cubic_bezier(0.7, 0.0, 0.84, 0.0);
235    static ref EASE_OUT_EXPO: CubicBezierEasing = cubic_bezier(0.16, 1.0, 0.3, 1.0);
236    static ref EASE_IN_OUT_EXPO: CubicBezierEasing = cubic_bezier(0.87, 0.0, 0.13, 1.0);
237    static ref EASE_IN_CIRC: CubicBezierEasing = cubic_bezier(0.55, 0.0, 1.0, 0.45);
238    static ref EASE_OUT_CIRC: CubicBezierEasing = cubic_bezier(0.0, 0.55, 0.45, 1.0);
239    static ref EASE_IN_OUT_CIRC: CubicBezierEasing = cubic_bezier(0.85, 0.0, 0.15, 1.0);
240    static ref EASE_IN_BACK: CubicBezierEasing = cubic_bezier(0.36, 0.0, 0.66, -0.56);
241    static ref EASE_OUT_BACK: CubicBezierEasing = cubic_bezier(0.34, 1.56, 0.64, 1.0);
242    static ref EASE_IN_OUT_BACK: CubicBezierEasing = cubic_bezier(0.68, -0.6, 0.32, 1.6);
243}
244
245/// Linear easing which returns the `x` value as the `y` result. Has the same behavior as
246/// [Easing::Linear] or [Easing::default].
247#[derive(Clone, Debug)]
248pub struct LinearEasing;
249
250impl EasingFunction for LinearEasing {
251    fn calc(&self, x: f32) -> f32 {
252        x
253    }
254}
255
256/// Easing function defined by a cubic bezier curve with the start and end points fixed at `(0, 0)`
257/// and `(1, 1)`, i.e. only the control points are specified.
258///
259/// Most standard easing functions use `CubicBezierEasing`. Instances of this may be created and
260/// used in [Easing::Custom] in cases where the standard easings do not suffice.
261#[derive(Clone, Debug)]
262pub struct CubicBezierEasing {
263    segment: CubicBezierSegment<f32>,
264}
265
266impl CubicBezierEasing {
267    /// Creates a new [CubicBezierEasing] with control points `(x1, y1)` and `(x2, y2)`.
268    ///
269    /// To experiment with different curves, see: <https://cubic-bezier.com/>
270    pub fn new(x1: f32, y1: f32, x2: f32, y2: f32) -> Self {
271        Self {
272            segment: CubicBezierSegment {
273                from: Point::new(0.0, 0.0),
274                to: Point::new(1.0, 1.0),
275                ctrl1: Point::new(x1, y1),
276                ctrl2: Point::new(x2, y2),
277            },
278        }
279    }
280}
281
282impl EasingFunction for CubicBezierEasing {
283    fn calc(&self, x: f32) -> f32 {
284        self.segment.y(x)
285    }
286}
287
288fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> CubicBezierEasing {
289    CubicBezierEasing::new(x1, y1, x2, y2)
290}