mina_core/
timeline_helpers.rs

1//! Internal helper types used in the composition of [`Timeline`](crate::timeline::Timeline)
2//! implementations.
3//!
4//! These types are public because they need to be accessible to the timeline structs generated by
5//! the [`Animate`](../../mina_macros/derive.Animate.html) macro.
6
7use crate::{
8    easing::{Easing, EasingFunction},
9    interpolation::Lerp,
10    timeline::Keyframe,
11};
12use std::fmt::Debug;
13
14/// Partial timeline representing the animation path of a single value belonging to a collection of
15/// animation properties.
16///
17/// In a regular, CSS-style timeline, keyframes are not required to specify all (or any) properties.
18/// The expected behavior is that each property interpolates between the most recent keyframe that
19/// _did_ include the property, and the earliest subsequent keyframe that includes it. The
20/// implementation of this can be finicky and confusing, particularly when taking into account edge
21/// cases such as not having any keyframe at position 0% or 100%, which is perfectly within spec.
22///
23/// Sub-timelines solve two problems:
24///
25/// - First, they present a timeline view that is precomputed and optimized for evaluation on every
26///   frame, i.e. one that can determine in O(1) time which frames apply to any given timeline
27///   position, including 0% and 100% regardless of whether or not there are real keyframes defined
28///   at those positions.
29///
30/// - Second, since they operate on only a single value, they can be implemented as normal generic
31///   structs without the use of a proc macro, which improves testability and reduces the complexity
32///   of the generated timeline structs.
33///
34/// User code should normally not need to create or access a sub-timeline; it is an implementation
35/// detail of the [`Animate`](../../mina_macros/derive.Animate.html) macro output.
36#[derive(Clone, Debug)]
37pub struct SubTimeline<Value: Clone> {
38    frames: Vec<SplitKeyframe<Value>>,
39    frame_index_map: Vec<usize>,
40    start_frame_override: Option<SplitKeyframe<Value>>,
41}
42
43impl<Value: Clone + Lerp> SubTimeline<Value> {
44    /// Extract a single-valued sub-timeline from a sequence of multi-valued keyframes.
45    ///
46    /// # Arguments
47    ///
48    /// * `keyframes` - Sequence of original [Keyframe] values, generally whose `Data` argument is
49    ///   a struct generated by the [`Animate`](../../mina_macros/derive.Animate.html) macro, e.g.
50    ///   `FooKeyframe` for an animator defined on type `Foo`, which contains one [`Option`] for
51    ///   each animatable field.
52    ///
53    /// * `default_value` - Value of the timeline at the 0% (`0.0`) position, **if and only if**
54    ///   the `keyframes` do not start at 0%. Otherwise, this argument is ignored.
55    ///
56    /// * `default_easing` - Default easing to use. See
57    ///   [TimelineConfiguration::default_easing](crate::timeline::TimelineConfiguration::default_easing).
58    pub fn from_keyframes<'a, Data: 'a + Clone + Debug, ValueFn>(
59        keyframes: impl IntoIterator<Item = &'a Keyframe<Data>>,
60        default_value: Value,
61        get_value: ValueFn,
62        default_easing: Easing,
63    ) -> Self
64    where
65        ValueFn: Fn(&Data) -> Option<Value>,
66    {
67        let mut converted_frames = Vec::new();
68        let mut frame_index_map = Vec::new();
69        let mut current_easing = default_easing;
70        let mut has_frame_data = false;
71        for keyframe in keyframes.into_iter() {
72            // There must always be a frame at t = 0. If the original timeline does not specify one,
73            // add one with the default value.
74            if converted_frames.is_empty() && keyframe.normalized_time > 0.0 {
75                converted_frames.push(SplitKeyframe::new(
76                    0.0,
77                    default_value.clone(),
78                    current_easing.clone(),
79                ));
80            }
81            if let Some(data) = get_value(&keyframe.data) {
82                has_frame_data = true;
83                if let Some(easing) = &keyframe.easing {
84                    current_easing = easing.clone();
85                }
86                converted_frames.push(SplitKeyframe::new(
87                    keyframe.normalized_time,
88                    data,
89                    current_easing.clone(),
90                ));
91            }
92            frame_index_map.push(converted_frames.len().max(1) - 1);
93        }
94        if !has_frame_data {
95            return Self::empty();
96        }
97        let trailing_frame = match converted_frames.last() {
98            Some(frame) if frame.normalized_time < 1.0 =>
99            // There must always be a frame at t = 1. If the original timeline does not specify
100            // one, add one with the same value as the previous frame.
101            {
102                Some(frame.with_time(1.0))
103            }
104            _ => None,
105        };
106        if let Some(trailing_frame) = trailing_frame {
107            converted_frames.push(trailing_frame);
108        }
109        Self {
110            frames: converted_frames,
111            frame_index_map,
112            start_frame_override: None,
113        }
114    }
115
116    /// Sets an override value to substitute for the first keyframe (at 0%, or `0.0` normalized
117    /// time), which will be used only when [`value_at`](Self::value_at) is called with
118    /// `enable_start_override` set to `true`.
119    ///
120    /// This is typically used when blending animations; the newly-active timeline begins where the
121    /// previously-active timeline ended or was interrupted.
122    ///
123    /// If the sub-timeline is empty, i.e. not used, then this does nothing.
124    ///
125    /// # Arguments
126    ///
127    /// * `value` - New value to use for the 0% frame position.
128    pub fn override_start_value(&mut self, value: Value) {
129        if let Some(first_frame) = self.frames.first() {
130            self.start_frame_override = Some(first_frame.with_value(value));
131        }
132    }
133
134    /// Gets the value for this sub-timeline's property at a given position.
135    ///
136    /// Does not perform a full search of keyframes based on the time; instead this expects the
137    /// caller to first determine the keyframe index in the _master_ timeline (not this
138    /// sub-timeline) and provide it as the `index_hint`.
139    ///
140    /// # Arguments
141    ///
142    /// * `normalized_time` - Timeline position from 0% (`0.0`) to 100% (`1.0`). Values outside this
143    ///   range are clamped to the range.
144    /// * `index_hint` - Index of the keyframe containing the `normalized_time` in the original
145    ///   timeline that was provided to [`from_keyframes`](Self::from_keyframes) on creation.
146    /// * `enable_start_override` - Whether to use the overridden value from a previous
147    ///   [`override_start_value`](Self::override_start_value) if the time is near the first frame.
148    ///   If this is `false`, the original value will be used irrespective of overrides.
149    pub fn value_at(
150        &self,
151        normalized_time: f32,
152        index_hint: usize,
153        enable_start_override: bool,
154    ) -> Option<Value> {
155        if self.frame_index_map.is_empty() {
156            return None;
157        }
158        let normalized_time = normalized_time.clamp(0.0, 1.0);
159        let bounding_frames =
160            self.get_bounding_frames(normalized_time, index_hint, enable_start_override)?;
161        Some(interpolate_value(&bounding_frames, normalized_time))
162    }
163
164    fn empty() -> Self {
165        Self {
166            frame_index_map: vec![],
167            frames: vec![],
168            start_frame_override: None,
169        }
170    }
171
172    fn get_bounding_frames(
173        &self,
174        normalized_time: f32,
175        index_hint: usize,
176        enable_start_override: bool,
177    ) -> Option<[&SplitKeyframe<Value>; 2]> {
178        let index_at = *self.frame_index_map.get(index_hint)?;
179        let frame_at = self.get_frame(index_at, enable_start_override)?;
180        if normalized_time < frame_at.normalized_time {
181            if index_at > 0 {
182                Some([
183                    self.get_frame(index_at - 1, enable_start_override)?,
184                    frame_at,
185                ])
186            } else {
187                None
188            }
189        } else if index_at == self.frames.len() - 1 {
190            Some([frame_at, frame_at])
191        } else {
192            self.frames
193                .get(index_at + 1)
194                .map(|next_frame| [frame_at, next_frame])
195        }
196    }
197
198    fn get_frame(
199        &self,
200        index: usize,
201        enable_start_override: bool,
202    ) -> Option<&SplitKeyframe<Value>> {
203        if enable_start_override && index == 0 {
204            if let Some(ref override_frame) = self.start_frame_override {
205                Some(override_frame)
206            } else {
207                self.frames.get(0)
208            }
209        } else {
210            self.frames.get(index)
211        }
212    }
213}
214
215/// Internal keyframe type used in a [SubTimeline].
216///
217/// This is referred to as a "split" keyframe because the original keyframes are _split_ into
218/// sub-timelines per animation property. The differences between a [Keyframe] and [SplitKeyframe]
219/// are:
220///
221/// * `Keyframe`s include the entire set of animatable properties as `Option`s. [SplitKeyframe]
222///   holds the value of only one property, and it is non-optional.
223///
224/// * `Keyframe`s specify an optional [Easing] that overrides whichever previous easing was used,
225///   and applies until a subsequent frame overrides it again; this means zero or some very small
226///   number of keyframes may have the field populated. `SplitKeyframe` always specifies an easing
227///   function, as determined by the aforementioned rules on `Keyframe`, so that the interpolation
228///   for any given timeline position does not require additional searching.
229#[derive(Clone, Debug)]
230struct SplitKeyframe<Value: Clone> {
231    easing: Easing,
232    normalized_time: f32,
233    value: Value,
234}
235
236impl<Value: Clone> SplitKeyframe<Value> {
237    fn new(normalized_time: f32, value: Value, easing: Easing) -> Self {
238        Self {
239            normalized_time,
240            value,
241            easing,
242        }
243    }
244
245    fn with_time(&self, normalized_time: f32) -> Self {
246        SplitKeyframe::new(normalized_time, self.value.clone(), self.easing.clone())
247    }
248
249    fn with_value(&self, value: Value) -> Self {
250        SplitKeyframe::new(self.normalized_time, value, self.easing.clone())
251    }
252}
253
254fn interpolate_value<Value: Clone + Lerp>(
255    bounding_frames: &[&SplitKeyframe<Value>; 2],
256    time: f32,
257) -> Value {
258    let [start_frame, end_frame] = bounding_frames;
259    let duration = end_frame.normalized_time - start_frame.normalized_time;
260    if duration == 0.0 {
261        return start_frame.value.clone();
262    }
263    // For parity with CSS spec, easing (timing function) is always taken from the "start" frame.
264    // Any easing defined on a keyframe at t = 1.0 is ignored.
265    // https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function#description
266    let easing = &start_frame.easing;
267    let x = (time - start_frame.normalized_time) / duration;
268    let y = easing.calc(x);
269    start_frame.value.lerp(&end_frame.value, y)
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275    use crate::timeline::Timeline;
276
277    #[derive(Clone, Debug, Default, PartialEq)]
278    struct TestValues {
279        foo: u8,
280        bar: f32,
281    }
282
283    impl TestValues {
284        pub fn new(foo: u8, bar: f32) -> Self {
285            Self { foo, bar }
286        }
287
288        // A normal values wouldn't have this, but it helps with testing given the lack of floating
289        // point precision.
290        pub fn round(&self) -> Self {
291            Self {
292                foo: self.foo,
293                bar: self.bar.round(),
294            }
295        }
296    }
297
298    #[derive(Clone, Debug)]
299    struct TestKeyframeData {
300        foo: Option<u8>,
301        bar: Option<f32>,
302    }
303
304    impl TestKeyframeData {
305        fn new(foo: Option<u8>, bar: Option<f32>) -> Self {
306            Self { foo, bar }
307        }
308
309        fn full(foo: u8, bar: f32) -> Self {
310            Self {
311                foo: Some(foo),
312                bar: Some(bar),
313            }
314        }
315    }
316
317    #[derive(Debug)]
318    struct TestTimeline {
319        boundary_times: Vec<f32>,
320        foo: SubTimeline<u8>,
321        bar: SubTimeline<f32>,
322    }
323
324    impl TestTimeline {
325        fn new(keyframes: Vec<Keyframe<TestKeyframeData>>, default_easing: Easing) -> Self {
326            let defaults = TestValues::default();
327            Self {
328                foo: SubTimeline::from_keyframes(
329                    &keyframes,
330                    defaults.foo,
331                    |k| k.foo,
332                    default_easing.clone(),
333                ),
334                bar: SubTimeline::from_keyframes(
335                    &keyframes,
336                    defaults.bar,
337                    |k| k.bar,
338                    default_easing,
339                ),
340                boundary_times: keyframes.iter().map(|k| k.normalized_time).collect(),
341            }
342        }
343
344        // A real timeline would have its own time scale with delay, duration, etc., which is
345        // different from the normalized time scale of the `SubTimeline`. This method would also
346        // handle the logic to distinguish repeating and reversing, which should never override
347        // start values, from the first forward cycle, which should. These differences aren't
348        // important for the purpose of unit-testing the sub; we'll just work in the normalized time
349        // ranges and provide an explicit way to choose override or non-override.
350        fn update_with_override(
351            &self,
352            target: &mut TestValues,
353            time: f32,
354            enable_start_override: bool,
355        ) {
356            if self.boundary_times.is_empty() {
357                return;
358            }
359            let frame_index = match self.boundary_times.binary_search_by(|t| t.total_cmp(&time)) {
360                Ok(index) => index,
361                Err(next_index) => next_index.max(1) - 1,
362            };
363            if let Some(foo) = self.foo.value_at(time, frame_index, enable_start_override) {
364                target.foo = foo;
365            }
366            if let Some(bar) = self.bar.value_at(time, frame_index, enable_start_override) {
367                target.bar = bar;
368            }
369        }
370
371        fn values_at(&self, time: f32) -> TestValues {
372            let mut target = TestValues::default();
373            self.update(&mut target, time);
374            target
375        }
376
377        fn non_overridden_values_at(&self, time: f32) -> TestValues {
378            let mut target = TestValues::default();
379            self.update_with_override(&mut target, time, false);
380            target
381        }
382
383        fn updated_values_at<'a>(&self, values: &TestValues, time: f32) -> TestValues {
384            let mut updated_values = values.clone();
385            self.update(&mut updated_values, time);
386            updated_values
387        }
388    }
389
390    impl Timeline for TestTimeline {
391        type Target = TestValues;
392
393        fn start_with(&mut self, values: &Self::Target) {
394            self.foo.override_start_value(values.foo);
395            self.bar.override_start_value(values.bar);
396        }
397
398        fn update(&self, target: &mut Self::Target, time: f32) {
399            self.update_with_override(target, time, true);
400        }
401    }
402
403    #[test]
404    fn when_empty_then_does_not_modify() {
405        let timeline = TestTimeline::new(vec![], Easing::default());
406
407        let initial_values = TestValues::new(123, 583.122);
408        assert_eq!(
409            timeline.updated_values_at(&initial_values, 0.0),
410            initial_values
411        );
412        assert_eq!(
413            timeline.updated_values_at(&initial_values, 0.5),
414            initial_values
415        );
416        assert_eq!(
417            timeline.updated_values_at(&initial_values, 1.0),
418            initial_values
419        );
420    }
421
422    #[test]
423    fn when_property_not_used_then_does_not_modify_property() {
424        let keyframes = vec![
425            Keyframe::new(0.4, TestKeyframeData::new(Some(40), None), None),
426            Keyframe::new(0.6, TestKeyframeData::new(Some(50), None), None),
427            Keyframe::new(0.6, TestKeyframeData::new(Some(80), None), None),
428        ];
429        let timeline = TestTimeline::new(keyframes, Easing::default());
430
431        let initial_values = TestValues::new(100, 0.123);
432        assert_eq!(
433            timeline.updated_values_at(&initial_values, 0.0),
434            TestValues::new(0, 0.123)
435        );
436        assert_eq!(
437            timeline.updated_values_at(&initial_values, 0.5),
438            TestValues::new(45, 0.123)
439        );
440        assert_eq!(
441            timeline.updated_values_at(&initial_values, 1.0),
442            TestValues::new(80, 0.123)
443        );
444    }
445
446    #[test]
447    fn when_no_keyframe_at_zero_then_interpolates_from_defaults() {
448        let keyframes = vec![
449            Keyframe::new(0.25, TestKeyframeData::new(None, Some(50.0)), None),
450            Keyframe::new(0.5, TestKeyframeData::new(Some(80), Some(200.0)), None),
451        ];
452        let timeline = TestTimeline::new(keyframes, Easing::default());
453
454        assert_eq!(timeline.values_at(0.0), TestValues::default());
455        assert_eq!(timeline.values_at(0.1), TestValues::new(16, 20.0));
456        assert_eq!(timeline.values_at(0.25), TestValues::new(40, 50.0));
457    }
458
459    #[test]
460    fn when_full_keyframe_at_zero_then_interpolates_from_first_keyframe() {
461        let keyframes = vec![
462            Keyframe::new(0.0, TestKeyframeData::full(10, 20.0), None),
463            Keyframe::new(0.4, TestKeyframeData::full(50, 200.0), None),
464        ];
465        let timeline = TestTimeline::new(keyframes, Easing::default());
466
467        assert_eq!(timeline.values_at(0.0), TestValues::new(10, 20.0));
468        assert_eq!(timeline.values_at(0.2), TestValues::new(30, 110.0));
469        assert_eq!(timeline.values_at(0.4), TestValues::new(50, 200.0));
470    }
471
472    #[test]
473    fn when_partial_keyframe_at_zero_then_interpolates_from_defaults_and_first_keyframe() {
474        let keyframes = vec![
475            Keyframe::new(0.0, TestKeyframeData::new(Some(10), None), None),
476            Keyframe::new(0.4, TestKeyframeData::full(50, 200.0), None),
477        ];
478        let timeline = TestTimeline::new(keyframes, Easing::default());
479
480        assert_eq!(timeline.values_at(0.0), TestValues::new(10, 0.0));
481        assert_eq!(timeline.values_at(0.2), TestValues::new(30, 100.0));
482        assert_eq!(timeline.values_at(0.4), TestValues::new(50, 200.0));
483    }
484
485    #[test]
486    fn when_no_keyframe_at_end_then_stays_at_last_keyframe() {
487        let keyframes = vec![
488            Keyframe::new(0.5, TestKeyframeData::new(Some(30), None), None),
489            Keyframe::new(0.75, TestKeyframeData::new(Some(50), Some(1000.0)), None),
490        ];
491        let timeline = TestTimeline::new(keyframes, Easing::default());
492
493        assert_eq!(timeline.values_at(0.75), TestValues::new(50, 1000.0));
494        assert_eq!(timeline.values_at(0.85), TestValues::new(50, 1000.0));
495        assert_eq!(timeline.values_at(1.0), TestValues::new(50, 1000.0));
496    }
497
498    #[test]
499    fn when_keyframe_at_end_then_interpolates_to_last_keyframe() {
500        let keyframes = vec![
501            Keyframe::new(0.25, TestKeyframeData::full(40, 250.0), None),
502            Keyframe::new(0.5, TestKeyframeData::full(20, 0.0), None),
503            Keyframe::new(1.0, TestKeyframeData::full(60, 1000.0), None),
504        ];
505        let timeline = TestTimeline::new(keyframes, Easing::default());
506
507        assert_eq!(timeline.values_at(0.5), TestValues::new(20, 0.0));
508        assert_eq!(timeline.values_at(0.75), TestValues::new(40, 500.0));
509        assert_eq!(timeline.values_at(1.0), TestValues::new(60, 1000.0));
510    }
511
512    #[test]
513    fn when_easing_not_overridden_then_interpolates_with_default_easing() {
514        let keyframes = vec![
515            Keyframe::new(0.0, TestKeyframeData::full(0, 0.0), None),
516            Keyframe::new(1.0, TestKeyframeData::full(40, 100.0), None),
517        ];
518        let timeline = TestTimeline::new(keyframes, Easing::OutQuad);
519
520        assert_eq!(timeline.values_at(0.0).round(), TestValues::new(0, 0.0));
521        assert_eq!(timeline.values_at(0.2).round(), TestValues::new(20, 49.0));
522        assert_eq!(timeline.values_at(0.4).round(), TestValues::new(31, 78.0));
523        assert_eq!(timeline.values_at(0.6).round(), TestValues::new(37, 94.0));
524        assert_eq!(timeline.values_at(0.8).round(), TestValues::new(40, 99.0));
525        assert_eq!(timeline.values_at(1.0).round(), TestValues::new(40, 100.0));
526    }
527
528    #[test]
529    fn when_easing_overridden_then_uses_new_easing() {
530        let keyframes = vec![
531            Keyframe::new(0.0, TestKeyframeData::full(0, 0.0), None),
532            Keyframe::new(0.2, TestKeyframeData::full(50, 100.0), None),
533            Keyframe::new(
534                0.4,
535                TestKeyframeData::full(100, 400.0),
536                Some(Easing::OutCirc),
537            ),
538            Keyframe::new(0.6, TestKeyframeData::full(150, 1000.0), None),
539            Keyframe::new(
540                0.8,
541                TestKeyframeData::full(200, 5000.0),
542                Some(Easing::InSine),
543            ),
544            Keyframe::new(1.0, TestKeyframeData::full(250, 10000.0), None),
545        ];
546        let timeline = TestTimeline::new(keyframes, Easing::default());
547
548        assert_eq!(timeline.values_at(0.0).round(), TestValues::new(0, 0.0));
549        assert_eq!(timeline.values_at(0.1).round(), TestValues::new(25, 50.0));
550        assert_eq!(timeline.values_at(0.2).round(), TestValues::new(50, 100.0));
551        assert_eq!(timeline.values_at(0.3).round(), TestValues::new(75, 250.0));
552        assert_eq!(timeline.values_at(0.4).round(), TestValues::new(100, 400.0));
553        assert_eq!(timeline.values_at(0.5).round(), TestValues::new(135, 824.0));
554        assert_eq!(
555            timeline.values_at(0.6).round(),
556            TestValues::new(150, 1000.0)
557        );
558        assert_eq!(
559            timeline.values_at(0.7).round(),
560            TestValues::new(185, 3825.0)
561        );
562        assert_eq!(
563            timeline.values_at(0.8).round(),
564            TestValues::new(200, 5000.0)
565        );
566        assert_eq!(
567            timeline.values_at(0.9).round(),
568            TestValues::new(206, 5625.0)
569        );
570        assert_eq!(
571            timeline.values_at(1.0).round(),
572            TestValues::new(250, 10000.0)
573        );
574    }
575
576    #[test]
577    fn when_start_value_overridden_then_updates_if_non_empty() {
578        let keyframes = vec![
579            Keyframe::new(0.0, TestKeyframeData::new(Some(10), None), None),
580            Keyframe::new(0.5, TestKeyframeData::new(Some(15), None), None),
581            Keyframe::new(1.0, TestKeyframeData::new(Some(50), None), None),
582        ];
583        let mut timeline = TestTimeline::new(keyframes, Easing::default());
584
585        timeline.start_with(&TestValues::new(5, 8.5));
586
587        assert_eq!(timeline.values_at(0.0), TestValues::new(5, 0.0));
588        assert_eq!(timeline.values_at(0.25), TestValues::new(10, 0.0));
589        assert_eq!(timeline.values_at(0.5), TestValues::new(15, 0.0));
590        assert_eq!(timeline.values_at(1.0), TestValues::new(50, 0.0));
591    }
592
593    #[test]
594    fn when_start_override_disabled_then_interpolates_with_original_keyframe() {
595        let keyframes = vec![
596            Keyframe::new(0.0, TestKeyframeData::new(Some(10), None), None),
597            Keyframe::new(0.5, TestKeyframeData::new(Some(15), None), None),
598            Keyframe::new(1.0, TestKeyframeData::new(Some(50), None), None),
599        ];
600        let mut timeline = TestTimeline::new(keyframes, Easing::default());
601
602        timeline.start_with(&TestValues::new(5, 8.5));
603
604        assert_eq!(
605            timeline.non_overridden_values_at(0.0),
606            TestValues::new(10, 0.0)
607        );
608        assert_eq!(
609            timeline.non_overridden_values_at(0.25),
610            TestValues::new(12, 0.0)
611        );
612        assert_eq!(
613            timeline.non_overridden_values_at(0.5),
614            TestValues::new(15, 0.0)
615        );
616        assert_eq!(
617            timeline.non_overridden_values_at(1.0),
618            TestValues::new(50, 0.0)
619        );
620    }
621}