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}