1use std::{fmt, sync::Arc};
4
5#[cfg(test)]
6mod tests;
7
8#[derive(Clone)]
25pub enum Easing {
26 Linear,
28 SineIn,
30 SineOut,
32 SineInOut,
34 QuadraticIn,
36 QuadraticOut,
38 QuadraticInOut,
40 CubicIn,
42 CubicOut,
44 CubicInOut,
46 QuarticIn,
48 QuarticOut,
50 QuarticInOut,
52 ExponentialIn,
54 ExponentialOut,
56 ExponentialInOut,
58 #[non_exhaustive]
60 BackIn {
61 overshoot: f64,
63 },
64 #[non_exhaustive]
66 BackOut {
67 overshoot: f64,
69 },
70 #[non_exhaustive]
73 BackInOut {
74 overshoot: f64,
76 },
77 #[non_exhaustive]
79 ElasticOut {
80 amplitude: f64,
82 period: f64,
84 },
85 BounceOut,
87 #[non_exhaustive]
89 Steps(u32),
90 #[non_exhaustive]
92 CubicBezier {
93 x1: f64,
95 y1: f64,
97 x2: f64,
99 y2: f64,
101 },
102 Custom(Arc<dyn Fn(f64) -> f64 + Send + Sync>),
105}
106
107impl Easing {
108 pub fn back_in(overshoot: f64) -> Result<Self, crate::ValidationError> {
110 check_overshoot(overshoot).map(|overshoot| Self::BackIn { overshoot })
111 }
112
113 pub fn back_out(overshoot: f64) -> Result<Self, crate::ValidationError> {
115 check_overshoot(overshoot).map(|overshoot| Self::BackOut { overshoot })
116 }
117
118 pub fn back_in_out(overshoot: f64) -> Result<Self, crate::ValidationError> {
120 check_overshoot(overshoot).map(|overshoot| Self::BackInOut { overshoot })
121 }
122
123 pub fn elastic_out(amplitude: f64, period: f64) -> Result<Self, crate::ValidationError> {
125 if !amplitude.is_finite() || amplitude < 1.0 || !period.is_finite() || period <= 0.0 {
126 return Err(easing_error(
127 "elastic",
128 "elastic amplitude must be finite and at least one; period must be finite and positive",
129 ));
130 }
131 Ok(Self::ElasticOut { amplitude, period })
132 }
133
134 pub fn steps(count: u32) -> Result<Self, crate::ValidationError> {
136 if count == 0 {
137 return Err(easing_error("steps", "easing steps must be nonzero"));
138 }
139 Ok(Self::Steps(count))
140 }
141
142 pub fn cubic_bezier(
144 x1: f64,
145 y1: f64,
146 x2: f64,
147 y2: f64,
148 ) -> Result<Self, crate::ValidationError> {
149 if ![x1, y1, x2, y2].iter().all(|value| value.is_finite())
150 || !(0.0..=1.0).contains(&x1)
151 || !(0.0..=1.0).contains(&x2)
152 {
153 return Err(easing_error(
154 "controls",
155 "Bezier controls must be finite with X in 0..=1",
156 ));
157 }
158 Ok(Self::CubicBezier { x1, y1, x2, y2 })
159 }
160
161 pub fn apply(&self, progress: f64) -> f64 {
168 let progress = progress.clamp(0.0, 1.0);
169 if progress == 0.0 && !matches!(self, Self::Custom(_)) {
170 return 0.0;
171 }
172 if progress == 1.0 && !matches!(self, Self::Custom(_)) {
173 return 1.0;
174 }
175 match self {
176 Self::Linear => progress,
177 Self::SineIn => {
178 let sine = libm::sin(progress * std::f64::consts::FRAC_PI_4);
179 2.0 * sine * sine
180 }
181 Self::SineOut => libm::sin(progress * std::f64::consts::FRAC_PI_2),
182 Self::SineInOut => {
183 if progress == 0.5 {
184 return 0.5;
185 }
186 let sine = libm::sin(progress * std::f64::consts::FRAC_PI_2);
187 sine * sine
188 }
189 Self::QuadraticIn => progress * progress,
190 Self::QuadraticOut => progress * (2.0 - progress),
191 Self::QuadraticInOut => mirrored(progress, |value| value * value),
192 Self::CubicIn => progress * progress * progress,
193 Self::CubicOut => progress * (3.0 + progress * (progress - 3.0)),
194 Self::CubicInOut => mirrored(progress, |value| value * value * value),
195 Self::QuarticIn => progress * progress * progress * progress,
196 Self::QuarticOut => progress * (4.0 + progress * (-6.0 + progress * (4.0 - progress))),
197 Self::QuarticInOut => mirrored(progress, |value| value * value * value * value),
198 Self::ExponentialIn => libm::exp2(10.0 * (progress - 1.0)),
199 Self::ExponentialOut => -libm::expm1(-10.0 * std::f64::consts::LN_2 * progress),
200 Self::ExponentialInOut => mirrored(progress, |value| libm::exp2(10.0 * (value - 1.0))),
201 Self::BackIn { overshoot } => back_in(progress, *overshoot),
202 Self::BackOut { overshoot } => 1.0 - back_in(1.0 - progress, *overshoot),
203 Self::BackInOut { overshoot } => mirrored(progress, |value| back_in(value, *overshoot)),
204 Self::ElasticOut { amplitude, period } => {
205 let phase = libm::asin(1.0 / amplitude) / std::f64::consts::TAU;
206 let cycles = libm::fmod(progress, *period) / period;
207 1.0 + amplitude
208 * libm::exp2(-10.0 * progress)
209 * libm::sin(std::f64::consts::TAU * (cycles - phase))
210 }
211 Self::BounceOut => bounce_out(progress),
212 Self::Steps(count) => libm::floor(progress * f64::from(*count)) / f64::from(*count),
213 Self::CubicBezier { x1, y1, x2, y2 } => cubic_bezier(progress, *x1, *y1, *x2, *y2),
214 Self::Custom(function) => function(progress),
215 }
216 }
217
218 pub fn try_apply(&self, progress: f64) -> Result<f64, crate::ValidationError> {
221 if !progress.is_finite() {
222 return Err(easing_error("progress", "easing progress must be finite"));
223 }
224 let value = self.apply(progress);
225 if !value.is_finite() {
226 return Err(easing_error("output", "easing output must be finite"));
227 }
228 Ok(value)
229 }
230
231 pub fn reversed(self) -> Self {
233 Self::Custom(Arc::new(move |progress| 1.0 - self.apply(1.0 - progress)))
234 }
235
236 pub fn mirrored(self) -> Self {
238 Self::Custom(Arc::new(move |progress| {
239 mirrored(progress, |value| self.apply(value))
240 }))
241 }
242}
243
244impl fmt::Debug for Easing {
245 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
246 match self {
247 Self::Linear => formatter.write_str("Linear"),
248 Self::SineIn => formatter.write_str("SineIn"),
249 Self::SineOut => formatter.write_str("SineOut"),
250 Self::SineInOut => formatter.write_str("SineInOut"),
251 Self::QuadraticIn => formatter.write_str("QuadraticIn"),
252 Self::QuadraticOut => formatter.write_str("QuadraticOut"),
253 Self::QuadraticInOut => formatter.write_str("QuadraticInOut"),
254 Self::CubicIn => formatter.write_str("CubicIn"),
255 Self::CubicOut => formatter.write_str("CubicOut"),
256 Self::CubicInOut => formatter.write_str("CubicInOut"),
257 Self::QuarticIn => formatter.write_str("QuarticIn"),
258 Self::QuarticOut => formatter.write_str("QuarticOut"),
259 Self::QuarticInOut => formatter.write_str("QuarticInOut"),
260 Self::ExponentialIn => formatter.write_str("ExponentialIn"),
261 Self::ExponentialOut => formatter.write_str("ExponentialOut"),
262 Self::ExponentialInOut => formatter.write_str("ExponentialInOut"),
263 Self::BackIn { overshoot } => formatter
264 .debug_struct("BackIn")
265 .field("overshoot", overshoot)
266 .finish(),
267 Self::BackOut { overshoot } => formatter
268 .debug_struct("BackOut")
269 .field("overshoot", overshoot)
270 .finish(),
271 Self::BackInOut { overshoot } => formatter
272 .debug_struct("BackInOut")
273 .field("overshoot", overshoot)
274 .finish(),
275 Self::ElasticOut { amplitude, period } => formatter
276 .debug_struct("ElasticOut")
277 .field("amplitude", amplitude)
278 .field("period", period)
279 .finish(),
280 Self::BounceOut => formatter.write_str("BounceOut"),
281 Self::Steps(count) => formatter.debug_tuple("Steps").field(count).finish(),
282 Self::CubicBezier { x1, y1, x2, y2 } => formatter
283 .debug_struct("CubicBezier")
284 .field("x1", x1)
285 .field("y1", y1)
286 .field("x2", x2)
287 .field("y2", y2)
288 .finish(),
289 Self::Custom(_) => formatter.write_str("Custom(..)"),
290 }
291 }
292}
293
294fn check_overshoot(overshoot: f64) -> Result<f64, crate::ValidationError> {
295 if overshoot.is_finite() && overshoot >= 0.0 {
296 Ok(overshoot)
297 } else {
298 Err(easing_error(
299 "overshoot",
300 "back overshoot must be finite and nonnegative",
301 ))
302 }
303}
304
305fn easing_error(field: &str, message: &str) -> crate::ValidationError {
306 crate::ValidationError::invalid("easing", field, message)
307}
308
309fn mirrored(progress: f64, inward: impl Fn(f64) -> f64) -> f64 {
310 if progress < 0.5 {
311 inward(2.0 * progress) * 0.5
312 } else {
313 1.0 - inward(2.0 * (1.0 - progress)) * 0.5
314 }
315}
316
317fn back_in(progress: f64, overshoot: f64) -> f64 {
318 progress * progress * (progress + overshoot * (progress - 1.0))
319}
320
321fn bounce_out(progress: f64) -> f64 {
322 let (shifted, offset) = if progress < 1.0 / 2.75 {
323 (progress, 0.0)
324 } else if progress < 2.0 / 2.75 {
325 (progress - 1.5 / 2.75, 0.75)
326 } else if progress < 2.5 / 2.75 {
327 (progress - 2.25 / 2.75, 0.9375)
328 } else {
329 (progress - 2.625 / 2.75, 0.984375)
330 };
331 7.5625 * shifted * shifted + offset
332}
333
334fn coordinate(parameter: f64, first: f64, second: f64) -> f64 {
335 let inverse = 1.0 - parameter;
336 let first_weight = 3.0 * inverse * inverse * parameter;
337 let second_weight = 3.0 * inverse * parameter * parameter;
338 first_weight * first + second_weight * second + parameter * parameter * parameter
339}
340
341fn cubic_bezier(progress: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
342 coordinate(bezier_parameter(progress, x1, x2), y1, y2)
343}
344
345fn bezier_parameter(progress: f64, x1: f64, x2: f64) -> f64 {
346 if progress > 0.5 {
347 return 1.0 - bezier_parameter(1.0 - progress, 1.0 - x2, 1.0 - x1);
348 }
349 let mut lower = 0.0;
350 let mut upper = 1.0;
351 for _ in 0..1_100 {
352 let parameter = lower + (upper - lower) * 0.5;
353 let horizontal = coordinate(parameter, x1, x2);
354 if horizontal == progress || parameter == lower || parameter == upper {
355 return parameter;
356 }
357 if horizontal < progress {
358 lower = parameter;
359 } else {
360 upper = parameter;
361 }
362 }
363 lower + (upper - lower) * 0.5
364}