Skip to main content

motion_core/
animation.rs

1//! Clock-free easing and value interpolation.
2
3use std::{marker::PhantomData, sync::Arc};
4
5use crate::{Duration, Point, Time, Transform, Vector2};
6
7mod easing;
8mod keyframes;
9mod primitives;
10mod spring;
11#[cfg(test)]
12mod tests;
13
14pub use easing::Easing;
15pub use keyframes::Keyframes;
16pub use primitives::{
17    Extrapolate, FollowPath, Noise, Oscillator, PathSample, Waveform, piecewise, remap, value_noise,
18};
19pub use spring::Spring;
20
21const BEFORE_START: Time = Time::from_ticks(-1);
22
23/// Values that can interpolate between endpoints. Progress may overshoot `0.0..=1.0`.
24/// Implement this trait to sample custom values with [`Tween`] and [`Keyframes`].
25/// Progress and geometry use double precision. f32 values retain their
26/// own arithmetic; colors extrapolate Oklab and clamp only alpha.
27pub trait Interpolate: Clone {
28    /// Blends toward `target` with nominal progress zero at self and one at target.
29    ///
30    /// Custom implementations should be deterministic and handle overshoot
31    /// intentionally. Built-in floats/pairs extrapolate; bounds preserve
32    /// signed extrapolated dimensions. Colors extrapolate Oklab and clamp alpha.
33    fn interpolate(&self, target: &Self, progress: f64) -> Self;
34}
35
36impl Interpolate for f32 {
37    fn interpolate(&self, target: &Self, progress: f64) -> Self {
38        let progress = progress as f32;
39        self * (1.0 - progress) + target * progress
40    }
41}
42
43impl Interpolate for f64 {
44    fn interpolate(&self, target: &Self, progress: f64) -> Self {
45        self * (1.0 - progress) + target * progress
46    }
47}
48
49impl Interpolate for Point {
50    fn interpolate(&self, target: &Self, progress: f64) -> Self {
51        Point::interpolate(self, target, progress)
52    }
53}
54
55impl Interpolate for Vector2 {
56    fn interpolate(&self, target: &Self, progress: f64) -> Self {
57        Vector2::interpolate(self, target, progress)
58    }
59}
60
61impl Interpolate for Transform {
62    fn interpolate(&self, target: &Self, progress: f64) -> Self {
63        Transform::interpolate(self, target, progress)
64    }
65}
66
67impl<Value: Interpolate, const COUNT: usize> Interpolate for [Value; COUNT] {
68    fn interpolate(&self, target: &Self, progress: f64) -> Self {
69        std::array::from_fn(|index| self[index].interpolate(&target[index], progress))
70    }
71}
72
73impl Interpolate for crate::Color {
74    fn interpolate(&self, target: &Self, progress: f64) -> Self {
75        crate::Color::interpolate(self, target, progress)
76    }
77}
78
79impl Interpolate for crate::Size {
80    fn interpolate(&self, target: &Self, progress: f64) -> Self {
81        Self::new(
82            self.width.interpolate(&target.width, progress),
83            self.height.interpolate(&target.height, progress),
84        )
85    }
86}
87
88impl Interpolate for crate::Rect {
89    fn interpolate(&self, target: &Self, progress: f64) -> Self {
90        Self::new(
91            self.left.interpolate(&target.left, progress),
92            self.top.interpolate(&target.top, progress),
93            self.right.interpolate(&target.right, progress),
94            self.bottom.interpolate(&target.bottom, progress),
95        )
96    }
97}
98
99impl<First: Interpolate, Second: Interpolate> Interpolate for (First, Second) {
100    fn interpolate(&self, target: &Self, progress: f64) -> Self {
101        (
102            self.0.interpolate(&target.0, progress),
103            self.1.interpolate(&target.1, progress),
104        )
105    }
106}
107
108/// A thread-safe, randomly addressable animation of values.
109///
110/// Finite implementations hold their endpoint values outside `[0, duration]`.
111/// Infinite implementations may use negative time. Sampling must not depend on
112/// earlier calls. A reported duration must be nonnegative and stable.
113pub trait Animation<Value>: Send + Sync {
114    /// Samples without advancing a clock or changing playback state.
115    fn sample(&self, time: Time) -> Value;
116
117    /// Returns the finite duration, or `None` for an unbounded signal.
118    fn duration(&self) -> Option<Duration> {
119        None
120    }
121
122    /// Transforms values without changing timing.
123    fn map<Output, Function>(self, function: Function) -> Map<Self, Function, Value>
124    where
125        Self: Sized,
126        Function: Fn(Value) -> Output + Send + Sync,
127    {
128        Map {
129            source: self,
130            function,
131            input: PhantomData,
132        }
133    }
134
135    /// Samples in parallel; the duration is infinite if either input is infinite.
136    fn zip<OtherValue, Other>(self, other: Other) -> Zip<Self, Other>
137    where
138        Self: Sized,
139        Other: Animation<OtherValue>,
140    {
141        Zip {
142            first: self,
143            second: other,
144        }
145    }
146
147    /// Delays a finite animation, holding its initial value before the offset.
148    /// Infinite signals shift their signed time instead. Negative offsets and
149    /// finite duration overflow panic at construction.
150    fn delay(self, offset: Duration) -> Delay<Self>
151    where
152        Self: Sized,
153    {
154        assert!(offset >= Time::ZERO, "animation delay must be nonnegative");
155        let duration = checked_duration(self.duration()).map(|duration| duration + offset);
156        Delay {
157            source: self,
158            offset,
159            duration,
160        }
161    }
162
163    /// Changes playback speed by a finite, strictly positive factor.
164    /// Durations round to the nearest tick; a collapsed finite animation becomes
165    /// instantaneous. Invalid factors and unrepresentable durations panic.
166    fn speed(self, factor: f64) -> Speed<Self>
167    where
168        Self: Sized,
169    {
170        assert!(
171            factor.is_finite() && factor > 0.0,
172            "animation speed must be finite and positive"
173        );
174        let source_duration = checked_duration(self.duration());
175        let duration = source_duration.map(|duration| duration / factor);
176        Speed {
177            source: self,
178            factor,
179            source_duration,
180            duration,
181        }
182    }
183
184    /// Runs two finite animations in sequence. The second owns their shared
185    /// boundary. Infinite sources or duration overflow panic at construction.
186    fn then<Next: Animation<Value>>(self, next: Next) -> Then<Self, Next>
187    where
188        Self: Sized,
189    {
190        let first_duration = finite_duration(self.duration(), "then");
191        let next_duration = finite_duration(next.duration(), "then");
192        Then {
193            first: self,
194            next,
195            first_duration,
196            duration: first_duration + next_duration,
197        }
198    }
199
200    /// Reverses a finite animation. Infinite sources panic at construction.
201    fn reverse(self) -> Reverse<Self>
202    where
203        Self: Sized,
204    {
205        let duration = finite_duration(self.duration(), "reverse");
206        Reverse {
207            source: self,
208            duration,
209        }
210    }
211
212    /// Plays a finite animation `count` times. Intermediate boundaries restart;
213    /// the final boundary holds the target. Zero repeats hold the initial value.
214    /// Infinite sources or duration overflow panic at construction.
215    fn repeat(self, count: u32) -> Repeat<Self>
216    where
217        Self: Sized,
218    {
219        let source_duration = finite_duration(self.duration(), "repeat");
220        let duration = cycle_duration(source_duration, count);
221        Repeat {
222            source: self,
223            count,
224            source_duration,
225            duration,
226        }
227    }
228
229    /// Plays a finite animation forward then backward, ending at its initial
230    /// value. Infinite sources or duration overflow panic at construction.
231    fn ping_pong(self) -> PingPong<Self>
232    where
233        Self: Sized,
234    {
235        let source_duration = finite_duration(self.duration(), "ping_pong");
236        let duration = cycle_duration(source_duration, 2);
237        PingPong {
238            source: self,
239            source_duration,
240            duration,
241        }
242    }
243
244    /// Shares a type-erased animation, retaining sampling and combinators.
245    fn boxed(self) -> Arc<dyn Animation<Value>>
246    where
247        Self: Sized + 'static,
248    {
249        Arc::new(self)
250    }
251}
252
253impl<Value, Function> Animation<Value> for Function
254where
255    Function: Fn(Time) -> Value + Send + Sync,
256{
257    fn sample(&self, time: Time) -> Value {
258        self(time)
259    }
260}
261
262impl<Value> Animation<Value> for Arc<dyn Animation<Value> + '_> {
263    fn sample(&self, time: Time) -> Value {
264        self.as_ref().sample(time)
265    }
266
267    fn duration(&self) -> Option<Duration> {
268        self.as_ref().duration()
269    }
270}
271
272/// An unbounded constant signal.
273#[derive(Clone, Debug)]
274pub struct Constant<Value> {
275    value: Value,
276}
277
278impl<Value> Constant<Value> {
279    /// Stores the value returned at every signed time.
280    pub const fn new(value: Value) -> Self {
281        Self { value }
282    }
283}
284
285impl<Value: Clone + Send + Sync> Animation<Value> for Constant<Value> {
286    fn sample(&self, _time: Time) -> Value {
287        self.value.clone()
288    }
289}
290
291/// A finite endpoint-to-endpoint interpolation with double-precision progress.
292///
293/// ```
294/// use motion_core::prelude::*;
295/// use motion_core::{
296///     DisplayList, RasterLimits, ResourcePool, ValidatedDisplayList, MAX_PATH_VERBS,
297///     MAX_STROKE_VERBS, MAX_SVG_PATH_BYTES, sample_offsets,
298/// };
299/// let movement = Tween::new(Point::ORIGIN, Point::new(100.0, 40.0), 2.seconds());
300/// assert_eq!(movement.sample(1.seconds()), Point::new(50.0, 20.0));
301/// assert_eq!(movement.sample((-1).seconds()), Point::ORIGIN);
302/// ```
303#[derive(Clone, Debug)]
304pub struct Tween<Value> {
305    from: Value,
306    to: Value,
307    duration: Duration,
308    easing: Easing,
309}
310
311impl<Value> Tween<Value> {
312    /// Creates a linear tween. A negative duration panics at construction.
313    /// A zero duration holds `from` before zero and `to` at/after zero.
314    pub fn new(from: Value, to: Value, duration: Duration) -> Self {
315        Self::try_new(from, to, duration).unwrap_or_else(|error| panic!("{}", error.message))
316    }
317
318    /// Creates a tween from dynamic input without panicking for negative duration.
319    pub fn try_new(
320        from: Value,
321        to: Value,
322        duration: Duration,
323    ) -> Result<Self, crate::ValidationError> {
324        if duration < Time::ZERO {
325            return Err(crate::ValidationError::invalid(
326                "tween",
327                "duration",
328                "tween duration must be nonnegative",
329            ));
330        }
331        Ok(Self {
332            from,
333            to,
334            duration,
335            easing: Easing::Linear,
336        })
337    }
338
339    /// Selects a f64 curve.
340    /// Invalid curve parameters panic during construction.
341    pub fn easing(mut self, easing: Easing) -> Self {
342        self.easing = easing.validated();
343        self
344    }
345
346    /// Selects a curve after recoverable validation of its built-in parameters.
347    pub fn try_easing(mut self, easing: Easing) -> Result<Self, crate::ValidationError> {
348        easing.try_validate()?;
349        self.easing = easing;
350        Ok(self)
351    }
352}
353
354impl<Value: Interpolate + Send + Sync> Animation<Value> for Tween<Value> {
355    fn sample(&self, time: Time) -> Value {
356        if time < Time::ZERO {
357            return self.from.clone();
358        }
359        if time >= self.duration {
360            return self.to.clone();
361        }
362        if time == Time::ZERO {
363            return self.from.clone();
364        }
365        let progress = self.easing.apply(time / self.duration);
366        self.from.interpolate(&self.to, progress)
367    }
368
369    fn duration(&self) -> Option<Duration> {
370        Some(self.duration)
371    }
372}
373
374/// Value mapping produced by [`Animation::map`].
375#[derive(Clone, Debug)]
376pub struct Map<Source, Function, Input> {
377    source: Source,
378    function: Function,
379    input: PhantomData<fn(Input)>,
380}
381
382impl<Input, Output, Source, Function> Animation<Output> for Map<Source, Function, Input>
383where
384    Source: Animation<Input>,
385    Function: Fn(Input) -> Output + Send + Sync,
386{
387    fn sample(&self, time: Time) -> Output {
388        (self.function)(self.source.sample(time))
389    }
390
391    fn duration(&self) -> Option<Duration> {
392        self.source.duration()
393    }
394}
395
396/// Parallel sampling produced by [`Animation::zip`].
397#[derive(Clone, Debug)]
398pub struct Zip<First, Second> {
399    first: First,
400    second: Second,
401}
402
403impl<FirstValue, SecondValue, First, Second> Animation<(FirstValue, SecondValue)>
404    for Zip<First, Second>
405where
406    First: Animation<FirstValue>,
407    Second: Animation<SecondValue>,
408{
409    fn sample(&self, time: Time) -> (FirstValue, SecondValue) {
410        (self.first.sample(time), self.second.sample(time))
411    }
412
413    fn duration(&self) -> Option<Duration> {
414        Some(self.first.duration()?.max(self.second.duration()?))
415    }
416}
417
418fn checked_duration(duration: Option<Duration>) -> Option<Duration> {
419    if let Some(duration) = duration {
420        assert!(
421            duration >= Time::ZERO,
422            "animation duration must be nonnegative"
423        );
424    }
425    duration
426}
427
428fn finite_duration(duration: Option<Duration>, operation: &str) -> Duration {
429    checked_duration(duration).unwrap_or_else(|| panic!("{operation} requires a finite animation"))
430}
431
432fn cycle_duration(duration: Duration, count: u32) -> Duration {
433    Time::from_ticks(
434        duration
435            .ticks()
436            .checked_mul(i64::from(count))
437            .expect("animation duration overflow"),
438    )
439}
440
441/// A delayed animation produced by [`Animation::delay`].
442#[derive(Clone, Debug)]
443pub struct Delay<Source> {
444    source: Source,
445    offset: Duration,
446    duration: Option<Duration>,
447}
448
449impl<Value, Source: Animation<Value>> Animation<Value> for Delay<Source> {
450    fn sample(&self, time: Time) -> Value {
451        let local = match self.duration {
452            Some(_) if time < self.offset => BEFORE_START,
453            Some(duration) if time >= duration => duration - self.offset,
454            _ => time - self.offset,
455        };
456        self.source.sample(local)
457    }
458
459    fn duration(&self) -> Option<Duration> {
460        self.duration
461    }
462}
463
464/// A time-scaled animation produced by [`Animation::speed`].
465#[derive(Clone, Debug)]
466pub struct Speed<Source> {
467    source: Source,
468    factor: f64,
469    source_duration: Option<Duration>,
470    duration: Option<Duration>,
471}
472
473impl<Value, Source: Animation<Value>> Animation<Value> for Speed<Source> {
474    fn sample(&self, time: Time) -> Value {
475        let local = match (self.duration, self.source_duration) {
476            (Some(_), _) if time < Time::ZERO => BEFORE_START,
477            (Some(duration), Some(source_duration)) if time >= duration => source_duration,
478            (Some(_), Some(_)) if self.factor == 1.0 => time,
479            (Some(_), Some(source_duration)) => Time::from_ticks(
480                (time.ticks() as f64 * self.factor)
481                    .round()
482                    .clamp(0.0, source_duration.ticks() as f64) as i64,
483            ),
484            _ => time * self.factor,
485        };
486        self.source.sample(local)
487    }
488
489    fn duration(&self) -> Option<Duration> {
490        self.duration
491    }
492}
493
494/// A finite sequence produced by [`Animation::then`].
495#[derive(Clone, Debug)]
496pub struct Then<First, Next> {
497    first: First,
498    next: Next,
499    first_duration: Duration,
500    duration: Duration,
501}
502
503impl<Value, First: Animation<Value>, Next: Animation<Value>> Animation<Value>
504    for Then<First, Next>
505{
506    fn sample(&self, time: Time) -> Value {
507        if time < Time::ZERO {
508            self.first.sample(BEFORE_START)
509        } else if time >= self.duration {
510            self.next.sample(self.duration - self.first_duration)
511        } else if time < self.first_duration {
512            self.first.sample(time)
513        } else {
514            self.next.sample(time - self.first_duration)
515        }
516    }
517
518    fn duration(&self) -> Option<Duration> {
519        Some(self.duration)
520    }
521}
522
523/// A reversed finite animation produced by [`Animation::reverse`].
524#[derive(Clone, Debug)]
525pub struct Reverse<Source> {
526    source: Source,
527    duration: Duration,
528}
529
530impl<Value, Source: Animation<Value>> Animation<Value> for Reverse<Source> {
531    fn sample(&self, time: Time) -> Value {
532        let local = if time < Time::ZERO {
533            self.duration
534        } else if time >= self.duration {
535            BEFORE_START
536        } else {
537            self.duration - time
538        };
539        self.source.sample(local)
540    }
541
542    fn duration(&self) -> Option<Duration> {
543        Some(self.duration)
544    }
545}
546
547/// A finite repetition produced by [`Animation::repeat`].
548#[derive(Clone, Debug)]
549pub struct Repeat<Source> {
550    source: Source,
551    count: u32,
552    source_duration: Duration,
553    duration: Duration,
554}
555
556impl<Value, Source: Animation<Value>> Animation<Value> for Repeat<Source> {
557    fn sample(&self, time: Time) -> Value {
558        let local = if self.count == 0 || time < Time::ZERO {
559            BEFORE_START
560        } else if time >= self.duration {
561            self.source_duration
562        } else {
563            Time::from_ticks(time.ticks() % self.source_duration.ticks())
564        };
565        self.source.sample(local)
566    }
567
568    fn duration(&self) -> Option<Duration> {
569        Some(self.duration)
570    }
571}
572
573/// A finite forward/backward cycle produced by [`Animation::ping_pong`].
574#[derive(Clone, Debug)]
575pub struct PingPong<Source> {
576    source: Source,
577    source_duration: Duration,
578    duration: Duration,
579}
580
581impl<Value, Source: Animation<Value>> Animation<Value> for PingPong<Source> {
582    fn sample(&self, time: Time) -> Value {
583        let local = if time < Time::ZERO || time >= self.duration {
584            BEFORE_START
585        } else if time <= self.source_duration {
586            time
587        } else {
588            self.duration - time
589        };
590        self.source.sample(local)
591    }
592
593    fn duration(&self) -> Option<Duration> {
594        Some(self.duration)
595    }
596}
597
598#[cfg(test)]
599mod interpolation_tests {
600    use super::*;
601
602    #[test]
603    fn interpolation_preserves_double_precision_progress() {
604        #[derive(Clone)]
605        struct CustomValue(f64);
606
607        impl Interpolate for CustomValue {
608            fn interpolate(&self, _target: &Self, progress: f64) -> Self {
609                Self(progress)
610            }
611        }
612
613        fn blend<Value: Interpolate>(from: Value, to: Value, progress: f64) -> Value {
614            from.interpolate(&to, progress)
615        }
616
617        let progress = 0.500_000_000_1;
618        assert_ne!(progress, f64::from(progress as f32));
619        assert_eq!(blend(0.0_f64, 1.0, progress), progress);
620        assert_eq!(
621            blend(CustomValue(0.0), CustomValue(1.0), progress).0,
622            progress
623        );
624        let point = blend(Point::ORIGIN, Point::new(1.0, 2.0), progress);
625        assert_eq!(point.x, progress);
626        assert_eq!(point.y, 2.0 * progress);
627        assert_eq!(
628            blend(Vector2::ZERO, Vector2::new(1.0, 0.0), progress).x,
629            progress
630        );
631        let transform = blend(
632            Transform::IDENTITY,
633            Transform::translate(1.0, 0.0),
634            progress,
635        );
636        assert_eq!(transform.apply(Point::ORIGIN).x, progress);
637        let array = blend([0.0_f64; 2], [1.0_f64; 2], progress);
638        assert_eq!(array, [progress; 2]);
639        assert_eq!(
640            blend((0.0_f64, 0.0_f64), (1.0, 2.0), progress),
641            (progress, 2.0 * progress)
642        );
643    }
644}