1use std::rc::Rc;
2use std::time::Duration;
3
4use gpui::{ease_in_out, ease_out_quint, linear, quadratic, Animation};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
8pub enum Ease {
9 Linear,
10 EaseIn,
11 #[default]
12 EaseOut,
13 EaseInOut,
14 EaseOutQuint,
15}
16
17impl Ease {
18 pub(crate) fn function(self) -> Rc<dyn Fn(f32) -> f32> {
19 match self {
20 Self::Linear => Rc::new(linear),
21 Self::EaseIn => Rc::new(quadratic),
22 Self::EaseOut => Rc::new(ease_out_cubic),
23 Self::EaseInOut => Rc::new(ease_in_out),
24 Self::EaseOutQuint => Rc::new(ease_out_quint()),
25 }
26 }
27}
28
29pub fn ease_out_cubic(t: f32) -> f32 {
30 let t = t.clamp(0.0, 1.0);
31 1.0 - (1.0 - t).powi(3)
32}
33
34pub fn ease_in_cubic(t: f32) -> f32 {
35 let t = t.clamp(0.0, 1.0);
36 t * t * t
37}
38
39pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
40 move |t: f32| {
41 let t = t.clamp(0.0, 1.0);
42 let one_t = 1.0 - t;
43 let one_t2 = one_t * one_t;
44 let t2 = t * t;
45 let t3 = t2 * t;
46 let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3;
47 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3
48 }
49}
50
51pub(crate) fn spring_out(t: f32, bounce: f32) -> f32 {
52 let t = t.clamp(0.0, 1.0);
53 if bounce <= 0.001 {
54 return ease_out_cubic(t);
55 }
56 let freq = 1.0 + bounce * 2.5;
57 let decay = (-5.0 * t).exp();
58 let wave = (t * freq * std::f32::consts::TAU).sin();
59 (1.0 - decay + decay * wave * bounce * 0.45).clamp(0.0, 1.0 + bounce * 0.2)
60}
61
62#[derive(Clone)]
63enum TransitionKind {
64 Tween {
65 duration: Duration,
66 ease: Ease,
67 },
68 Spring {
69 duration: Duration,
70 bounce: f32,
71 },
72 SpringPhysics {
73 stiffness: f32,
74 damping: f32,
75 mass: f32,
76 },
77}
78
79#[derive(Clone)]
81pub struct Transition {
82 kind: TransitionKind,
83 delay: Duration,
84 repeat: bool,
85 pub(crate) stagger_children: Duration,
87 pub(crate) delay_children: Duration,
89}
90
91impl Default for Transition {
92 fn default() -> Self {
93 Self::tween(Duration::from_millis(300)).ease(Ease::EaseOut)
94 }
95}
96
97impl Transition {
98 pub fn tween(duration: Duration) -> Self {
99 Self {
100 kind: TransitionKind::Tween {
101 duration,
102 ease: Ease::EaseOut,
103 },
104 delay: Duration::ZERO,
105 repeat: false,
106 stagger_children: Duration::ZERO,
107 delay_children: Duration::ZERO,
108 }
109 }
110
111 pub fn spring() -> Self {
112 Self {
113 kind: TransitionKind::Spring {
114 duration: Duration::from_millis(500),
115 bounce: 0.25,
116 },
117 delay: Duration::ZERO,
118 repeat: false,
119 stagger_children: Duration::ZERO,
120 delay_children: Duration::ZERO,
121 }
122 }
123
124 pub fn ease(mut self, ease: Ease) -> Self {
125 if let TransitionKind::Tween {
126 ease: ref mut e, ..
127 } = self.kind
128 {
129 *e = ease;
130 }
131 self
132 }
133
134 pub fn duration(mut self, duration: Duration) -> Self {
135 match &mut self.kind {
136 TransitionKind::Tween {
137 duration: ref mut d,
138 ..
139 }
140 | TransitionKind::Spring {
141 duration: ref mut d,
142 ..
143 } => *d = duration,
144 TransitionKind::SpringPhysics { .. } => {
145 self.kind = TransitionKind::Spring {
146 duration,
147 bounce: 0.25,
148 };
149 }
150 }
151 self
152 }
153
154 pub fn bounce(mut self, bounce: f32) -> Self {
155 let bounce = bounce.clamp(0.0, 1.0);
156 match &mut self.kind {
157 TransitionKind::Spring {
158 bounce: ref mut b, ..
159 } => *b = bounce,
160 _ => {
161 let duration = self.resolved_duration();
162 self.kind = TransitionKind::Spring { duration, bounce };
163 }
164 }
165 self
166 }
167
168 pub fn stiffness(mut self, stiffness: f32) -> Self {
169 self.ensure_physics();
170 if let TransitionKind::SpringPhysics {
171 stiffness: ref mut s,
172 ..
173 } = self.kind
174 {
175 *s = stiffness.max(1.0);
176 }
177 self
178 }
179
180 pub fn damping(mut self, damping: f32) -> Self {
181 self.ensure_physics();
182 if let TransitionKind::SpringPhysics {
183 damping: ref mut d, ..
184 } = self.kind
185 {
186 *d = damping.max(0.0);
187 }
188 self
189 }
190
191 pub fn mass(mut self, mass: f32) -> Self {
192 self.ensure_physics();
193 if let TransitionKind::SpringPhysics {
194 mass: ref mut m, ..
195 } = self.kind
196 {
197 *m = mass.max(0.01);
198 }
199 self
200 }
201
202 pub fn delay(mut self, delay: Duration) -> Self {
203 self.delay = delay;
204 self
205 }
206
207 pub fn repeat(mut self) -> Self {
208 self.repeat = true;
209 self
210 }
211
212 pub fn stagger_children(mut self, stagger: Duration) -> Self {
214 self.stagger_children = stagger;
215 self
216 }
217
218 pub fn delay_children(mut self, delay: Duration) -> Self {
220 self.delay_children = delay;
221 self
222 }
223
224 fn ensure_physics(&mut self) {
225 if !matches!(self.kind, TransitionKind::SpringPhysics { .. }) {
226 self.kind = TransitionKind::SpringPhysics {
227 stiffness: 300.0,
228 damping: 25.0,
229 mass: 1.0,
230 };
231 }
232 }
233
234 pub(crate) fn resolved_duration(&self) -> Duration {
235 match self.kind {
236 TransitionKind::Tween { duration, .. } | TransitionKind::Spring { duration, .. } => {
237 duration
238 }
239 TransitionKind::SpringPhysics {
240 stiffness,
241 damping,
242 mass,
243 } => {
244 let omega = (stiffness / mass).sqrt();
245 let zeta = damping / (2.0 * (stiffness * mass).sqrt());
246 let seconds = if zeta < 1.0 {
247 4.0 / (zeta * omega).max(0.1)
248 } else {
249 4.0 / omega.max(0.1)
250 };
251 Duration::from_secs_f32(seconds.clamp(0.12, 2.0))
252 }
253 }
254 }
255
256 fn resolved_bounce(&self) -> f32 {
257 match self.kind {
258 TransitionKind::Spring { bounce, .. } => bounce,
259 TransitionKind::SpringPhysics {
260 stiffness,
261 damping,
262 mass,
263 } => {
264 let zeta = damping / (2.0 * (stiffness * mass).sqrt()).max(0.001);
265 (1.0 - zeta.clamp(0.0, 1.0)) * 0.5
266 }
267 TransitionKind::Tween { .. } => 0.0,
268 }
269 }
270
271 pub(crate) fn with_extra_delay(mut self, extra: Duration) -> Self {
272 self.delay = self.delay.saturating_add(extra);
273 self
274 }
275
276 pub(crate) fn into_animation(self) -> Animation {
277 let duration = self.resolved_duration().saturating_add(self.delay);
278 let bounce = self.resolved_bounce();
279 let delay_frac = if duration.as_secs_f32() > 0.0 {
280 self.delay.as_secs_f32() / duration.as_secs_f32()
281 } else {
282 0.0
283 };
284
285 let easing: Rc<dyn Fn(f32) -> f32> = match self.kind {
286 TransitionKind::Tween { ease, .. } => {
287 let ease = ease.function();
288 Rc::new(move |t: f32| gated_ease(t, delay_frac, |local| ease(local)))
289 }
290 TransitionKind::Spring { .. } | TransitionKind::SpringPhysics { .. } => {
291 Rc::new(move |t: f32| gated_ease(t, delay_frac, |local| spring_out(local, bounce)))
292 }
293 };
294
295 let mut animation = Animation::new(duration).with_easing(move |t| easing(t));
296 if self.repeat {
297 animation = animation.repeat();
298 }
299 animation
300 }
301}
302
303fn gated_ease(t: f32, delay_frac: f32, ease: impl Fn(f32) -> f32) -> f32 {
304 if t < delay_frac {
305 0.0
306 } else {
307 let local = ((t - delay_frac) / (1.0 - delay_frac).max(0.0001)).clamp(0.0, 1.0);
308 ease(local)
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::{ease_out_cubic, spring_out};
315
316 #[test]
317 fn ease_out_ends_at_one() {
318 assert!((ease_out_cubic(1.0) - 1.0).abs() < 0.001);
319 assert!(ease_out_cubic(0.0).abs() < 0.001);
320 }
321
322 #[test]
323 fn spring_out_settles() {
324 assert!(spring_out(0.0, 0.25).abs() < 0.05);
325 assert!((spring_out(1.0, 0.25) - 1.0).abs() < 0.05);
326 }
327}