pub mod ease;
mod animated;
mod animator;
mod clip;
mod frame;
mod macros;
mod motion;
mod motioned;
mod presence;
mod prop;
mod sequence;
mod spring;
mod stagger;
mod value;
pub use animated::Animated;
pub use animator::{Animator, AnimatorEvent};
pub use clip::Clip;
pub use ease::Curve;
pub use frame::Frame;
pub use motion::{IntoKeyframe, Keyframe, Loop, Motion, Track, SLIDE_DISTANCE};
pub use motioned::Motioned;
pub use presence::{Presence, PresenceEvent};
pub use prop::Prop;
pub use sequence::{At, Sequence};
pub use spring::Spring;
pub use stagger::{Stagger, StaggerAxis, StaggerFrom};
pub use value::AnimValue;
use std::time::Duration;
use gpui::Animation;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub enum Easing {
Linear,
EaseIn,
#[default]
EaseOut,
EaseInOut,
EaseInCubic,
EaseOutCubic,
EaseInOutCubic,
EaseOutQuint,
EaseOutExpo,
EaseOutBack,
EaseOutElastic,
EaseOutBounce,
In(Curve),
Out(Curve),
InOut(Curve),
Steps(u32),
CubicBezier(f32, f32, f32, f32),
Spring(Spring),
}
impl Easing {
pub fn apply(self, t: f32) -> f32 {
match self {
Easing::Linear => ease::linear(t),
Easing::EaseIn => ease::ease_in(t),
Easing::EaseOut => ease::ease_out(t),
Easing::EaseInOut => ease::ease_in_out(t),
Easing::EaseInCubic => ease::ease_in_cubic(t),
Easing::EaseOutCubic => ease::ease_out_cubic(t),
Easing::EaseInOutCubic => ease::ease_in_out_cubic(t),
Easing::EaseOutQuint => ease::ease_out_quint(t),
Easing::EaseOutExpo => ease::ease_out_expo(t),
Easing::EaseOutBack => ease::ease_out_back(t),
Easing::EaseOutElastic => ease::ease_out_elastic(t),
Easing::EaseOutBounce => ease::ease_out_bounce(t),
Easing::In(curve) => ease::curve_in(curve, t),
Easing::Out(curve) => ease::curve_out(curve, t),
Easing::InOut(curve) => ease::curve_in_out(curve, t),
Easing::Steps(count) => ease::steps(count, t),
Easing::CubicBezier(x1, y1, x2, y2) => ease::cubic_bezier(x1, y1, x2, y2, t),
Easing::Spring(spring) => spring.easing()(t),
}
}
pub fn animation(self, duration_ms: u64) -> Animation {
self.clock(duration_ms)
.with_easing(move |t| self.apply(t).clamp(0.0, 1.0))
}
pub fn clock(self, duration_ms: u64) -> Animation {
let duration = match self {
Easing::Spring(spring) => Duration::from_secs_f32(spring.settle_seconds()),
_ => Duration::from_millis(duration_ms),
};
Animation::new(duration)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_variant_hits_the_endpoints() {
let variants = [
Easing::Linear,
Easing::EaseIn,
Easing::EaseOut,
Easing::EaseInOut,
Easing::EaseInCubic,
Easing::EaseOutCubic,
Easing::EaseInOutCubic,
Easing::EaseOutQuint,
Easing::EaseOutExpo,
Easing::EaseOutBack,
Easing::EaseOutElastic,
Easing::EaseOutBounce,
Easing::CubicBezier(0.25, 0.1, 0.25, 1.0),
Easing::Spring(Spring::default()),
Easing::Steps(4),
];
let variants = variants.into_iter().chain(
Curve::ALL
.iter()
.flat_map(|c| [Easing::In(*c), Easing::Out(*c), Easing::InOut(*c)]),
);
for easing in variants {
assert!(easing.apply(0.0).abs() < 1e-3, "{easing:?} at 0");
assert!((easing.apply(1.0) - 1.0).abs() < 1e-3, "{easing:?} at 1");
}
}
#[test]
fn overshooting_curves_really_overshoot() {
let overshooters = [
Easing::EaseOutBack,
Easing::EaseOutElastic,
Easing::Spring(Spring::default()),
Easing::Out(Curve::Back),
Easing::Out(Curve::Elastic),
];
for easing in overshooters {
let peak = (1..100)
.map(|i| easing.apply(i as f32 / 100.0))
.fold(f32::MIN, f32::max);
assert!(peak > 1.0, "{easing:?} never exceeded 1.0 (peak {peak})");
}
}
}