Skip to main content

gpui_base/motion/
timing.rs

1use std::time::Duration;
2
3use super::Easing;
4
5#[derive(Clone, Copy, Debug, Eq, PartialEq)]
6pub enum SignedDuration {
7    Positive(Duration),
8    Negative(Duration),
9}
10
11impl SignedDuration {
12    pub const ZERO: Self = Self::Positive(Duration::ZERO);
13
14    pub const fn positive(duration: Duration) -> Self {
15        Self::Positive(duration)
16    }
17
18    pub const fn negative(duration: Duration) -> Self {
19        Self::Negative(duration)
20    }
21
22    pub(crate) fn active_elapsed(self, elapsed: Duration) -> Option<Duration> {
23        match self {
24            Self::Positive(delay) => elapsed.checked_sub(delay),
25            Self::Negative(delay) => Some(elapsed.saturating_add(delay)),
26        }
27    }
28}
29
30impl Default for SignedDuration {
31    fn default() -> Self {
32        Self::ZERO
33    }
34}
35
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum IterationCount {
38    Finite(u64),
39    Infinite,
40}
41
42impl Default for IterationCount {
43    fn default() -> Self {
44        Self::Finite(1)
45    }
46}
47
48#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
49pub enum PlaybackDirection {
50    #[default]
51    Normal,
52    Reverse,
53    Alternate,
54    AlternateReverse,
55}
56
57#[derive(Clone, Copy, Debug, Eq, PartialEq)]
58pub enum MotionPhase {
59    Before,
60    Active,
61    After,
62}
63
64#[derive(Clone, Copy, Debug, PartialEq)]
65pub struct TimingSample {
66    pub phase: MotionPhase,
67    pub directed_progress: f32,
68    pub iteration: u64,
69    pub active: bool,
70    pub finished: bool,
71}
72
73#[derive(Clone, Debug)]
74pub struct Timing {
75    delay: SignedDuration,
76    duration: Duration,
77    iterations: IterationCount,
78    direction: PlaybackDirection,
79    easing: Easing,
80}
81
82impl Timing {
83    pub fn new(duration: Duration) -> Self {
84        Self {
85            delay: SignedDuration::ZERO,
86            duration,
87            iterations: IterationCount::Finite(1),
88            direction: PlaybackDirection::Normal,
89            easing: Easing::Linear,
90        }
91    }
92
93    pub fn delay(mut self, delay: SignedDuration) -> Self {
94        self.delay = delay;
95        self
96    }
97
98    pub fn iterations(mut self, iterations: IterationCount) -> Self {
99        self.iterations = iterations;
100        self
101    }
102
103    pub fn direction(mut self, direction: PlaybackDirection) -> Self {
104        self.direction = direction;
105        self
106    }
107
108    pub fn ease(mut self, easing: Easing) -> Self {
109        self.easing = easing;
110        self
111    }
112
113    pub fn sample(&self, elapsed: Duration) -> TimingSample {
114        let Some(active_elapsed) = self.delay.active_elapsed(elapsed) else {
115            return TimingSample {
116                phase: MotionPhase::Before,
117                directed_progress: self.easing.sample(self.directed(0, 0.0)),
118                iteration: 0,
119                active: false,
120                finished: false,
121            };
122        };
123
124        let finite_iterations = match self.iterations {
125            IterationCount::Finite(count) => Some(count),
126            IterationCount::Infinite => None,
127        };
128        if self.duration.is_zero() || finite_iterations == Some(0) {
129            return self.after_sample(finite_iterations.unwrap_or(1));
130        }
131
132        let duration_nanos = self.duration.as_nanos();
133        let elapsed_nanos = active_elapsed.as_nanos();
134        if let Some(count) = finite_iterations
135            && elapsed_nanos >= duration_nanos.saturating_mul(count as u128)
136        {
137            return self.after_sample(count);
138        }
139
140        let iteration = (elapsed_nanos / duration_nanos).min(u64::MAX as u128) as u64;
141        let iteration_nanos = elapsed_nanos % duration_nanos;
142        let progress = iteration_nanos as f64 / duration_nanos as f64;
143        TimingSample {
144            phase: MotionPhase::Active,
145            directed_progress: self
146                .easing
147                .sample(self.directed(iteration, progress as f32)),
148            iteration,
149            active: true,
150            finished: false,
151        }
152    }
153
154    fn after_sample(&self, count: u64) -> TimingSample {
155        let iteration = count.saturating_sub(1);
156        TimingSample {
157            phase: MotionPhase::After,
158            directed_progress: self.easing.sample(self.directed(iteration, 1.0)),
159            iteration,
160            active: false,
161            finished: true,
162        }
163    }
164
165    fn directed(&self, iteration: u64, progress: f32) -> f32 {
166        let reverse = match self.direction {
167            PlaybackDirection::Normal => false,
168            PlaybackDirection::Reverse => true,
169            PlaybackDirection::Alternate => iteration % 2 == 1,
170            PlaybackDirection::AlternateReverse => iteration % 2 == 0,
171        };
172        if reverse { 1.0 - progress } else { progress }
173    }
174}