Skip to main content

gpui_base/motion/
keyframes.rs

1use std::sync::Arc;
2
3use super::{Easing, Interpolate};
4
5#[derive(Clone, Debug)]
6pub struct Keyframe<T> {
7    pub offset: f32,
8    pub value: T,
9    pub easing: Easing,
10}
11
12impl<T> Keyframe<T> {
13    pub fn new(offset: f32, value: T) -> Self {
14        Self {
15            offset,
16            value,
17            easing: Easing::Linear,
18        }
19    }
20
21    pub fn ease(mut self, easing: Easing) -> Self {
22        self.easing = easing;
23        self
24    }
25}
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum KeyframeError {
29    TooFewFrames,
30    OffsetNotFinite,
31    OffsetOutOfRange,
32    OffsetsNotMonotonic,
33    MissingEndpoint,
34}
35
36#[derive(Clone, Debug)]
37pub struct Keyframes<T> {
38    frames: Arc<[Keyframe<T>]>,
39}
40
41impl<T: Interpolate> Keyframes<T> {
42    pub fn try_new(frames: impl IntoIterator<Item = Keyframe<T>>) -> Result<Self, KeyframeError> {
43        let frames: Vec<_> = frames.into_iter().collect();
44        if frames.len() < 2 {
45            return Err(KeyframeError::TooFewFrames);
46        }
47        if frames.iter().any(|frame| !frame.offset.is_finite()) {
48            return Err(KeyframeError::OffsetNotFinite);
49        }
50        if frames
51            .iter()
52            .any(|frame| !(0.0..=1.0).contains(&frame.offset))
53        {
54            return Err(KeyframeError::OffsetOutOfRange);
55        }
56        if frames
57            .windows(2)
58            .any(|pair| pair[0].offset > pair[1].offset)
59        {
60            return Err(KeyframeError::OffsetsNotMonotonic);
61        }
62        if frames.first().unwrap().offset != 0.0 || frames.last().unwrap().offset != 1.0 {
63            return Err(KeyframeError::MissingEndpoint);
64        }
65        Ok(Self {
66            frames: frames.into(),
67        })
68    }
69
70    #[inline]
71    pub fn sample(&self, progress: f32) -> T {
72        let progress = progress.clamp(0.0, 1.0);
73        let upper = self
74            .frames
75            .partition_point(|frame| frame.offset <= progress);
76        if upper == 0 {
77            return self.frames[0].value.clone();
78        }
79        if upper == self.frames.len() {
80            return self.frames[self.frames.len() - 1].value.clone();
81        }
82        let from = &self.frames[upper - 1];
83        let to = &self.frames[upper];
84        if from.offset == to.offset {
85            return to.value.clone();
86        }
87        let segment = (progress - from.offset) / (to.offset - from.offset);
88        from.value
89            .interpolate(&to.value, from.easing.sample(segment))
90    }
91
92    pub fn len(&self) -> usize {
93        self.frames.len()
94    }
95
96    pub fn is_empty(&self) -> bool {
97        self.frames.is_empty()
98    }
99}
100
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub enum DiscreteError {
103    InvalidSwitchPoint,
104}
105
106#[derive(Clone, Debug)]
107pub struct Discrete<T> {
108    from: T,
109    to: T,
110    switch_at: f32,
111}
112
113impl<T> Discrete<T> {
114    pub fn new(from: T, to: T) -> Self {
115        Self {
116            from,
117            to,
118            switch_at: 0.5,
119        }
120    }
121
122    pub fn switch_at(mut self, progress: f32) -> Result<Self, DiscreteError> {
123        if !progress.is_finite() || !(0.0..=1.0).contains(&progress) {
124            return Err(DiscreteError::InvalidSwitchPoint);
125        }
126        self.switch_at = progress;
127        Ok(self)
128    }
129}
130
131impl<T: Clone> Discrete<T> {
132    #[inline]
133    pub fn sample(&self, progress: f32) -> T {
134        if progress < self.switch_at {
135            self.from.clone()
136        } else {
137            self.to.clone()
138        }
139    }
140}