Skip to main content

argui_animation/
keyframe.rs

1use crate::{Easing, Interpolate, TimingError};
2
3#[derive(Clone, Debug, PartialEq)]
4pub struct Keyframe<T> {
5    pub offset: f32,
6    pub value: T,
7    pub easing: Easing,
8    pub hold: bool,
9}
10
11impl<T> Keyframe<T> {
12    #[must_use]
13    pub fn new(offset: f32, value: T) -> Self {
14        Self {
15            offset,
16            value,
17            easing: Easing::Linear,
18            hold: false,
19        }
20    }
21
22    #[must_use]
23    pub fn easing(mut self, easing: Easing) -> Self {
24        self.easing = easing;
25        self
26    }
27
28    #[must_use]
29    pub const fn hold(mut self) -> Self {
30        self.hold = true;
31        self
32    }
33}
34
35#[derive(Clone, Debug, PartialEq)]
36pub struct Keyframes<T> {
37    frames: Box<[Keyframe<T>]>,
38}
39
40impl<T> Keyframes<T> {
41    pub fn new(frames: impl Into<Vec<Keyframe<T>>>) -> Result<Self, TimingError> {
42        let frames = frames.into();
43        if frames.len() < 2
44            || frames.first().is_none_or(|frame| frame.offset != 0.0)
45            || frames.last().is_none_or(|frame| frame.offset != 1.0)
46            || frames
47                .iter()
48                .any(|frame| !frame.offset.is_finite() || !(0.0..=1.0).contains(&frame.offset))
49            || frames
50                .windows(2)
51                .any(|pair| pair[0].offset > pair[1].offset)
52        {
53            return Err(TimingError::InvalidKeyframes);
54        }
55        Ok(Self {
56            frames: frames.into_boxed_slice(),
57        })
58    }
59
60    #[must_use]
61    pub fn as_slice(&self) -> &[Keyframe<T>] {
62        &self.frames
63    }
64}
65
66impl<T: Clone + Interpolate> Keyframes<T> {
67    #[must_use]
68    pub fn sample(&self, progress: f32) -> T {
69        let progress = progress.clamp(0.0, 1.0);
70        let upper = self
71            .frames
72            .partition_point(|frame| frame.offset <= progress);
73        if upper == 0 {
74            return self.frames[0].value.clone();
75        }
76        if upper == self.frames.len() {
77            return self.frames[upper - 1].value.clone();
78        }
79        let from = &self.frames[upper - 1];
80        let to = &self.frames[upper];
81        if from.hold || from.offset == to.offset {
82            return from.value.clone();
83        }
84        let local = (progress - from.offset) / (to.offset - from.offset);
85        from.value
86            .clone()
87            .interpolate(to.value.clone(), from.easing.sample(local))
88    }
89}