1pub mod ease;
37
38mod animated;
39mod animator;
40mod clip;
41mod frame;
42mod macros;
43mod motion;
44mod motioned;
45mod presence;
46mod prop;
47mod sequence;
48mod spring;
49mod stagger;
50mod value;
51
52pub use animated::Animated;
53pub use animator::{Animator, AnimatorEvent};
54pub use clip::Clip;
55pub use ease::Curve;
56pub use frame::Frame;
57pub use motion::{IntoKeyframe, Keyframe, Loop, Motion, Track, SLIDE_DISTANCE};
58pub use motioned::Motioned;
59pub use presence::{Presence, PresenceEvent};
60pub use prop::Prop;
61pub use sequence::{At, Sequence};
62pub use spring::Spring;
63pub use stagger::{Stagger, StaggerAxis, StaggerFrom};
64pub use value::AnimValue;
65
66use std::time::Duration;
67
68use gpui::Animation;
69
70#[derive(Debug, Clone, Copy, PartialEq, Default)]
73pub enum Easing {
74 Linear,
75 EaseIn,
76 #[default]
77 EaseOut,
78 EaseInOut,
79 EaseInCubic,
80 EaseOutCubic,
81 EaseInOutCubic,
82 EaseOutQuint,
83 EaseOutExpo,
84 EaseOutBack,
85 EaseOutElastic,
86 EaseOutBounce,
87 In(Curve),
90 Out(Curve),
92 InOut(Curve),
94 Steps(u32),
96 CubicBezier(f32, f32, f32, f32),
98 Spring(Spring),
100}
101
102impl Easing {
103 pub fn apply(self, t: f32) -> f32 {
104 match self {
105 Easing::Linear => ease::linear(t),
106 Easing::EaseIn => ease::ease_in(t),
107 Easing::EaseOut => ease::ease_out(t),
108 Easing::EaseInOut => ease::ease_in_out(t),
109 Easing::EaseInCubic => ease::ease_in_cubic(t),
110 Easing::EaseOutCubic => ease::ease_out_cubic(t),
111 Easing::EaseInOutCubic => ease::ease_in_out_cubic(t),
112 Easing::EaseOutQuint => ease::ease_out_quint(t),
113 Easing::EaseOutExpo => ease::ease_out_expo(t),
114 Easing::EaseOutBack => ease::ease_out_back(t),
115 Easing::EaseOutElastic => ease::ease_out_elastic(t),
116 Easing::EaseOutBounce => ease::ease_out_bounce(t),
117 Easing::In(curve) => ease::curve_in(curve, t),
118 Easing::Out(curve) => ease::curve_out(curve, t),
119 Easing::InOut(curve) => ease::curve_in_out(curve, t),
120 Easing::Steps(count) => ease::steps(count, t),
121 Easing::CubicBezier(x1, y1, x2, y2) => ease::cubic_bezier(x1, y1, x2, y2, t),
122 Easing::Spring(spring) => spring.easing()(t),
123 }
124 }
125
126 pub fn animation(self, duration_ms: u64) -> Animation {
143 self
144 .clock(duration_ms)
145 .with_easing(move |t| self.apply(t).clamp(0.0, 1.0))
146 }
147
148 pub fn clock(self, duration_ms: u64) -> Animation {
154 let duration = match self {
155 Easing::Spring(spring) => Duration::from_secs_f32(spring.settle_seconds()),
156 _ => Duration::from_millis(duration_ms),
157 };
158 Animation::new(duration)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn every_variant_hits_the_endpoints() {
168 let variants = [
169 Easing::Linear,
170 Easing::EaseIn,
171 Easing::EaseOut,
172 Easing::EaseInOut,
173 Easing::EaseInCubic,
174 Easing::EaseOutCubic,
175 Easing::EaseInOutCubic,
176 Easing::EaseOutQuint,
177 Easing::EaseOutExpo,
178 Easing::EaseOutBack,
179 Easing::EaseOutElastic,
180 Easing::EaseOutBounce,
181 Easing::CubicBezier(0.25, 0.1, 0.25, 1.0),
182 Easing::Spring(Spring::default()),
183 Easing::Steps(4),
184 ];
185 let variants = variants.into_iter().chain(
186 Curve::ALL
187 .iter()
188 .flat_map(|c| [Easing::In(*c), Easing::Out(*c), Easing::InOut(*c)]),
189 );
190 for easing in variants {
191 assert!(easing.apply(0.0).abs() < 1e-3, "{easing:?} at 0");
192 assert!((easing.apply(1.0) - 1.0).abs() < 1e-3, "{easing:?} at 1");
193 }
194 }
195
196 #[test]
200 fn overshooting_curves_really_overshoot() {
201 let overshooters = [
202 Easing::EaseOutBack,
203 Easing::EaseOutElastic,
204 Easing::Spring(Spring::default()),
205 Easing::Out(Curve::Back),
206 Easing::Out(Curve::Elastic),
207 ];
208 for easing in overshooters {
209 let peak = (1..100)
210 .map(|i| easing.apply(i as f32 / 100.0))
211 .fold(f32::MIN, f32::max);
212 assert!(peak > 1.0, "{easing:?} never exceeded 1.0 (peak {peak})");
213 }
214 }
215}