1use std::{fmt, sync::Arc};
4
5#[cfg(test)]
6mod tests;
7
8#[derive(Clone)]
24pub enum Easing {
25 Linear,
27 SineIn,
29 SineOut,
31 SineInOut,
33 QuadraticIn,
35 QuadraticOut,
37 QuadraticInOut,
39 CubicIn,
41 CubicOut,
43 CubicInOut,
45 QuarticIn,
47 QuarticOut,
49 QuarticInOut,
51 ExponentialIn,
53 ExponentialOut,
55 ExponentialInOut,
57 BackIn {
59 overshoot: f64,
61 },
62 BackOut {
64 overshoot: f64,
66 },
67 BackInOut {
69 overshoot: f64,
71 },
72 ElasticOut {
74 amplitude: f64,
76 period: f64,
78 },
79 BounceOut,
81 Steps(u32),
83 CubicBezier {
85 x1: f64,
87 y1: f64,
89 x2: f64,
91 y2: f64,
93 },
94 Custom(Arc<dyn Fn(f64) -> f64 + Send + Sync>),
97}
98
99impl Easing {
100 pub fn apply(&self, progress: f64) -> f64 {
106 self.try_apply(progress)
107 .unwrap_or_else(|error| panic!("{}", error.message))
108 }
109
110 pub fn try_apply(&self, progress: f64) -> Result<f64, crate::ValidationError> {
113 self.try_validate()?;
114 if !progress.is_finite() {
115 return Err(easing_error("progress", "easing progress must be finite"));
116 }
117 let progress = progress.clamp(0.0, 1.0);
118 if progress == 0.0 && !matches!(self, Self::Custom(_)) {
119 return Ok(0.0);
120 }
121 if progress == 1.0 && !matches!(self, Self::Custom(_)) {
122 return Ok(1.0);
123 }
124 let value = match self {
125 Self::Linear => progress,
126 Self::SineIn => {
127 let sine = libm::sin(progress * std::f64::consts::FRAC_PI_4);
128 2.0 * sine * sine
129 }
130 Self::SineOut => libm::sin(progress * std::f64::consts::FRAC_PI_2),
131 Self::SineInOut => {
132 if progress == 0.5 {
133 return Ok(0.5);
134 }
135 let sine = libm::sin(progress * std::f64::consts::FRAC_PI_2);
136 sine * sine
137 }
138 Self::QuadraticIn => progress * progress,
139 Self::QuadraticOut => progress * (2.0 - progress),
140 Self::QuadraticInOut => mirrored(progress, |value| value * value),
141 Self::CubicIn => progress * progress * progress,
142 Self::CubicOut => progress * (3.0 + progress * (progress - 3.0)),
143 Self::CubicInOut => mirrored(progress, |value| value * value * value),
144 Self::QuarticIn => progress * progress * progress * progress,
145 Self::QuarticOut => progress * (4.0 + progress * (-6.0 + progress * (4.0 - progress))),
146 Self::QuarticInOut => mirrored(progress, |value| value * value * value * value),
147 Self::ExponentialIn => libm::exp2(10.0 * (progress - 1.0)),
148 Self::ExponentialOut => -libm::expm1(-10.0 * std::f64::consts::LN_2 * progress),
149 Self::ExponentialInOut => mirrored(progress, |value| libm::exp2(10.0 * (value - 1.0))),
150 Self::BackIn { overshoot } => back_in(progress, *overshoot),
151 Self::BackOut { overshoot } => 1.0 - back_in(1.0 - progress, *overshoot),
152 Self::BackInOut { overshoot } => mirrored(progress, |value| back_in(value, *overshoot)),
153 Self::ElasticOut { amplitude, period } => {
154 let phase = libm::asin(1.0 / amplitude) / std::f64::consts::TAU;
155 let cycles = libm::fmod(progress, *period) / period;
156 1.0 + amplitude
157 * libm::exp2(-10.0 * progress)
158 * libm::sin(std::f64::consts::TAU * (cycles - phase))
159 }
160 Self::BounceOut => bounce_out(progress),
161 Self::Steps(count) => libm::floor(progress * f64::from(*count)) / f64::from(*count),
162 Self::CubicBezier { x1, y1, x2, y2 } => cubic_bezier(progress, *x1, *y1, *x2, *y2),
163 Self::Custom(function) => function(progress),
164 };
165 if !value.is_finite() {
166 return Err(easing_error("output", "easing output must be finite"));
167 }
168 Ok(value)
169 }
170
171 pub fn reversed(self) -> Self {
174 self.validate();
175 Self::Custom(Arc::new(move |progress| 1.0 - self.apply(1.0 - progress)))
176 }
177
178 pub fn mirrored(self) -> Self {
181 self.validate();
182 Self::Custom(Arc::new(move |progress| {
183 mirrored(progress, |value| self.apply(value))
184 }))
185 }
186
187 fn validate(&self) {
188 self.try_validate()
189 .unwrap_or_else(|error| panic!("{}", error.message));
190 }
191
192 pub fn try_validate(&self) -> Result<(), crate::ValidationError> {
194 match self {
195 Self::BackIn { overshoot }
196 | Self::BackOut { overshoot }
197 | Self::BackInOut { overshoot } => {
198 if !overshoot.is_finite() || *overshoot < 0.0 {
199 return Err(easing_error(
200 "overshoot",
201 "back overshoot must be finite and nonnegative",
202 ));
203 }
204 }
205 Self::ElasticOut { amplitude, period } => {
206 if !amplitude.is_finite()
207 || *amplitude < 1.0
208 || !period.is_finite()
209 || *period <= 0.0
210 {
211 return Err(easing_error(
212 "elastic",
213 "elastic amplitude must be finite and at least one; period must be finite and positive",
214 ));
215 }
216 }
217 Self::Steps(0) => return Err(easing_error("steps", "easing steps must be nonzero")),
218 Self::CubicBezier { x1, y1, x2, y2 }
219 if (![x1, y1, x2, y2].iter().all(|value| value.is_finite())
220 || !(0.0..=1.0).contains(x1)
221 || !(0.0..=1.0).contains(x2)) =>
222 {
223 return Err(easing_error(
224 "controls",
225 "Bezier controls must be finite with X in 0..=1",
226 ));
227 }
228 _ => {}
229 }
230 Ok(())
231 }
232
233 pub(super) fn validated(self) -> Self {
234 self.validate();
235 self
236 }
237}
238
239impl fmt::Debug for Easing {
240 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
241 match self {
242 Self::Linear => formatter.write_str("Linear"),
243 Self::SineIn => formatter.write_str("SineIn"),
244 Self::SineOut => formatter.write_str("SineOut"),
245 Self::SineInOut => formatter.write_str("SineInOut"),
246 Self::QuadraticIn => formatter.write_str("QuadraticIn"),
247 Self::QuadraticOut => formatter.write_str("QuadraticOut"),
248 Self::QuadraticInOut => formatter.write_str("QuadraticInOut"),
249 Self::CubicIn => formatter.write_str("CubicIn"),
250 Self::CubicOut => formatter.write_str("CubicOut"),
251 Self::CubicInOut => formatter.write_str("CubicInOut"),
252 Self::QuarticIn => formatter.write_str("QuarticIn"),
253 Self::QuarticOut => formatter.write_str("QuarticOut"),
254 Self::QuarticInOut => formatter.write_str("QuarticInOut"),
255 Self::ExponentialIn => formatter.write_str("ExponentialIn"),
256 Self::ExponentialOut => formatter.write_str("ExponentialOut"),
257 Self::ExponentialInOut => formatter.write_str("ExponentialInOut"),
258 Self::BackIn { overshoot } => formatter
259 .debug_struct("BackIn")
260 .field("overshoot", overshoot)
261 .finish(),
262 Self::BackOut { overshoot } => formatter
263 .debug_struct("BackOut")
264 .field("overshoot", overshoot)
265 .finish(),
266 Self::BackInOut { overshoot } => formatter
267 .debug_struct("BackInOut")
268 .field("overshoot", overshoot)
269 .finish(),
270 Self::ElasticOut { amplitude, period } => formatter
271 .debug_struct("ElasticOut")
272 .field("amplitude", amplitude)
273 .field("period", period)
274 .finish(),
275 Self::BounceOut => formatter.write_str("BounceOut"),
276 Self::Steps(count) => formatter.debug_tuple("Steps").field(count).finish(),
277 Self::CubicBezier { x1, y1, x2, y2 } => formatter
278 .debug_struct("CubicBezier")
279 .field("x1", x1)
280 .field("y1", y1)
281 .field("x2", x2)
282 .field("y2", y2)
283 .finish(),
284 Self::Custom(_) => formatter.write_str("Custom(..)"),
285 }
286 }
287}
288
289fn easing_error(field: &str, message: &str) -> crate::ValidationError {
290 crate::ValidationError::invalid("easing", field, message)
291}
292
293fn mirrored(progress: f64, inward: impl Fn(f64) -> f64) -> f64 {
294 if progress < 0.5 {
295 inward(2.0 * progress) * 0.5
296 } else {
297 1.0 - inward(2.0 * (1.0 - progress)) * 0.5
298 }
299}
300
301fn back_in(progress: f64, overshoot: f64) -> f64 {
302 progress * progress * (progress + overshoot * (progress - 1.0))
303}
304
305fn bounce_out(progress: f64) -> f64 {
306 let (shifted, offset) = if progress < 1.0 / 2.75 {
307 (progress, 0.0)
308 } else if progress < 2.0 / 2.75 {
309 (progress - 1.5 / 2.75, 0.75)
310 } else if progress < 2.5 / 2.75 {
311 (progress - 2.25 / 2.75, 0.9375)
312 } else {
313 (progress - 2.625 / 2.75, 0.984375)
314 };
315 7.5625 * shifted * shifted + offset
316}
317
318fn coordinate(parameter: f64, first: f64, second: f64) -> f64 {
319 let inverse = 1.0 - parameter;
320 let first_weight = 3.0 * inverse * inverse * parameter;
321 let second_weight = 3.0 * inverse * parameter * parameter;
322 first_weight * first + second_weight * second + parameter * parameter * parameter
323}
324
325fn cubic_bezier(progress: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 {
326 coordinate(bezier_parameter(progress, x1, x2), y1, y2)
327}
328
329fn bezier_parameter(progress: f64, x1: f64, x2: f64) -> f64 {
330 if progress > 0.5 {
331 return 1.0 - bezier_parameter(1.0 - progress, 1.0 - x2, 1.0 - x1);
332 }
333 let mut lower = 0.0;
334 let mut upper = 1.0;
335 for _ in 0..1_100 {
336 let parameter = lower + (upper - lower) * 0.5;
337 let horizontal = coordinate(parameter, x1, x2);
338 if horizontal == progress || parameter == lower || parameter == upper {
339 return parameter;
340 }
341 if horizontal < progress {
342 lower = parameter;
343 } else {
344 upper = parameter;
345 }
346 }
347 lower + (upper - lower) * 0.5
348}