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