gpui_base/motion/
timing.rs1use 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}