use kael::*;
use smallvec::SmallVec;
use std::time::Duration;
pub mod durations {
use std::time::Duration;
pub const ULTRA_FAST: Duration = Duration::from_millis(100);
pub const FASTEST: Duration = Duration::from_millis(150);
pub const FAST: Duration = Duration::from_millis(200);
pub const NORMAL: Duration = Duration::from_millis(300);
pub const SLOW: Duration = Duration::from_millis(400);
pub const SLOWEST: Duration = Duration::from_millis(500);
pub const EXTRA_SLOW: Duration = Duration::from_millis(600);
}
pub mod easings {
pub fn linear(t: f32) -> f32 {
t
}
pub fn ease_in_quad(t: f32) -> f32 {
t * t
}
pub fn ease_out_quad(t: f32) -> f32 {
t * (2.0 - t)
}
pub fn ease_in_out_quad(t: f32) -> f32 {
if t < 0.5 {
2.0 * t * t
} else {
-1.0 + (4.0 - 2.0 * t) * t
}
}
pub fn ease_in_cubic(t: f32) -> f32 {
t * t * t
}
pub fn ease_out_cubic(t: f32) -> f32 {
let t = t - 1.0;
t * t * t + 1.0
}
pub fn ease_in_out_cubic(t: f32) -> f32 {
if t < 0.5 {
4.0 * t * t * t
} else {
let t = 2.0 * t - 2.0;
1.0 + t * t * t / 2.0
}
}
pub fn ease_in_quart(t: f32) -> f32 {
t * t * t * t
}
pub fn ease_out_quart(t: f32) -> f32 {
let t = t - 1.0;
1.0 - t * t * t * t
}
pub fn ease_in_out_quart(t: f32) -> f32 {
if t < 0.5 {
8.0 * t * t * t * t
} else {
let t = t - 1.0;
1.0 - 8.0 * t * t * t * t
}
}
pub fn ease_out_expo(t: f32) -> f32 {
if t >= 1.0 {
1.0
} else {
1.0 - 2_f32.powf(-10.0 * t)
}
}
pub fn ease_in_out_expo(t: f32) -> f32 {
if t == 0.0 {
0.0
} else if t >= 1.0 {
1.0
} else if t < 0.5 {
2_f32.powf(20.0 * t - 10.0) / 2.0
} else {
(2.0 - 2_f32.powf(-20.0 * t + 10.0)) / 2.0
}
}
pub fn spring(t: f32) -> f32 {
if t >= 1.0 {
return 1.0;
}
let damping = 0.7;
let frequency = 1.5;
let decay = (-damping * t * 10.0).exp();
let oscillation = (frequency * t * std::f32::consts::PI * 2.0).sin();
let result = 1.0 - decay * oscillation * 0.5; result.clamp(0.0, 1.0)
}
pub fn elastic(t: f32) -> f32 {
if t == 0.0 {
return 0.0;
}
if t >= 1.0 {
return 1.0;
}
let p = 0.3;
let s = p / 4.0;
let t_adj = t - 1.0;
let result = 1.0
+ (2_f32.powf(10.0 * t_adj)) * ((t_adj - s) * (2.0 * std::f32::consts::PI) / p).sin();
result.clamp(0.0, 1.0)
}
pub fn smooth_spring(t: f32) -> f32 {
if t >= 1.0 {
return 1.0;
}
let damping = 0.9; let frequency = 1.0;
let decay = (-damping * t * 10.0).exp();
let oscillation = (frequency * t * std::f32::consts::PI * 2.0).sin();
let result = t + decay * oscillation * 0.1; result.clamp(0.0, 1.0)
}
pub fn ease_out_back(t: f32) -> f32 {
if t >= 1.0 {
return 1.0;
}
let c1 = 1.2; let c3 = c1 + 1.0;
let t_adj = t - 1.0;
let result = 1.0 + c3 * t_adj * t_adj * t_adj + c1 * t_adj * t_adj;
result.clamp(0.0, 1.0)
}
pub fn ease_in_expo(t: f32) -> f32 {
if t == 0.0 {
0.0
} else {
2_f32.powf(10.0 * t - 10.0)
}
}
pub fn ease_in_circ(t: f32) -> f32 {
1.0 - (1.0 - t * t).sqrt()
}
pub fn ease_out_circ(t: f32) -> f32 {
let t = t - 1.0;
(1.0 - t * t).sqrt()
}
pub fn ease_in_out_circ(t: f32) -> f32 {
if t < 0.5 {
(1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
} else {
((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
}
}
pub fn ease_in_back(t: f32) -> f32 {
let c1 = 1.70158;
let c3 = c1 + 1.0;
(c3 * t * t * t - c1 * t * t).max(0.0)
}
pub fn ease_in_out_back(t: f32) -> f32 {
let c1 = 1.70158;
let c2 = c1 * 1.525;
if t < 0.5 {
((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
} else {
let result =
((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (t * 2.0 - 2.0) + c2) + 2.0) / 2.0;
result.clamp(0.0, 1.0)
}
}
pub fn ease_in_elastic(t: f32) -> f32 {
if t == 0.0 {
return 0.0;
}
if t >= 1.0 {
return 1.0;
}
let c4 = (2.0 * std::f32::consts::PI) / 3.0;
let result = -(2_f32.powf(10.0 * t - 10.0) * ((t * 10.0 - 10.75) * c4).sin());
result.clamp(0.0, 1.0)
}
pub fn ease_out_elastic(t: f32) -> f32 {
if t == 0.0 {
return 0.0;
}
if t >= 1.0 {
return 1.0;
}
let c4 = (2.0 * std::f32::consts::PI) / 3.0;
let result = 2_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0;
result.clamp(0.0, 1.0)
}
pub fn ease_in_quint(t: f32) -> f32 {
t * t * t * t * t
}
pub fn ease_out_quint(t: f32) -> f32 {
let t = t - 1.0;
1.0 + t * t * t * t * t
}
pub fn ease_in_out_quint(t: f32) -> f32 {
if t < 0.5 {
16.0 * t * t * t * t * t
} else {
let t = 2.0 * t - 2.0;
1.0 + t * t * t * t * t / 2.0
}
}
pub fn steps(n: u32) -> impl Fn(f32) -> f32 {
move |t: f32| {
let n = n.max(1) as f32;
(t * n).floor() / n
}
}
pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 {
move |t: f32| {
if t <= 0.0 {
return 0.0;
}
if t >= 1.0 {
return 1.0;
}
let mut low = 0.0_f32;
let mut high = 1.0_f32;
let mut mid;
for _ in 0..20 {
mid = (low + high) / 2.0;
let x = cubic_bezier_sample(mid, x1, x2);
if (x - t).abs() < 0.0001 {
return cubic_bezier_sample(mid, y1, y2);
}
if x < t {
low = mid;
} else {
high = mid;
}
}
cubic_bezier_sample((low + high) / 2.0, y1, y2)
}
}
fn cubic_bezier_sample(t: f32, p1: f32, p2: f32) -> f32 {
let t2 = t * t;
let t3 = t2 * t;
3.0 * (1.0 - t) * (1.0 - t) * t * p1 + 3.0 * (1.0 - t) * t2 * p2 + t3
}
pub fn smooth() -> impl Fn(f32) -> f32 {
ease_in_out_cubic
}
pub fn snappy() -> impl Fn(f32) -> f32 {
ease_out_back
}
}
pub fn fade_in(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_out_cubic)
}
pub fn fade_out(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_in_cubic)
}
pub fn slide_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_out_cubic)
}
pub fn spring_slide(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::smooth_spring)
}
pub fn scale_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_out_back)
}
pub fn scale_smooth(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_out_cubic)
}
pub fn rotate_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::linear)
}
pub fn pulse_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::linear)
}
pub fn shake_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_out_quad)
}
pub fn bounce_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::spring)
}
pub fn bounce_smooth(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::ease_out_quart)
}
pub fn spring_animation(duration: Duration) -> Animation {
Animation::new(duration).with_easing(easings::smooth_spring)
}
pub mod presets {
use super::*;
pub fn fade_in_ultra_quick() -> Animation {
fade_in(durations::ULTRA_FAST)
}
pub fn fade_in_quick() -> Animation {
fade_in(durations::FAST)
}
pub fn fade_in_normal() -> Animation {
fade_in(durations::NORMAL)
}
pub fn fade_in_slow() -> Animation {
fade_in(durations::SLOW)
}
pub fn fade_out_quick() -> Animation {
fade_out(durations::FAST)
}
pub fn fade_out_normal() -> Animation {
fade_out(durations::NORMAL)
}
pub fn slide_in_top() -> Animation {
slide_animation(durations::NORMAL)
}
pub fn slide_in_bottom() -> Animation {
slide_animation(durations::NORMAL)
}
pub fn slide_in_left() -> Animation {
slide_animation(durations::NORMAL)
}
pub fn slide_in_right() -> Animation {
slide_animation(durations::NORMAL)
}
pub fn spring_slide_left() -> Animation {
spring_slide(durations::SLOW)
}
pub fn spring_slide_right() -> Animation {
spring_slide(durations::SLOW)
}
pub fn scale_up() -> Animation {
scale_animation(durations::FAST)
}
pub fn scale_down() -> Animation {
scale_animation(durations::FAST)
}
pub fn scale_up_smooth() -> Animation {
scale_smooth(durations::FAST)
}
pub fn scale_down_smooth() -> Animation {
scale_smooth(durations::FAST)
}
pub fn spin() -> Animation {
rotate_animation(Duration::from_secs(2)).repeat_forever()
}
pub fn spin_fast() -> Animation {
rotate_animation(Duration::from_secs(1)).repeat_forever()
}
pub fn spin_slow() -> Animation {
rotate_animation(Duration::from_secs(3)).repeat_forever()
}
pub fn pulse() -> Animation {
pulse_animation(Duration::from_secs(1)).repeat_forever()
}
pub fn pulse_fast() -> Animation {
pulse_animation(durations::EXTRA_SLOW).repeat_forever()
}
pub fn pulse_slow() -> Animation {
pulse_animation(Duration::from_millis(1500)).repeat_forever()
}
pub fn shake() -> Animation {
shake_animation(durations::FAST)
}
pub fn shake_strong() -> Animation {
shake_animation(durations::NORMAL)
}
pub fn bounce_in() -> Animation {
bounce_animation(durations::SLOW)
}
pub fn bounce_smooth_preset() -> Animation {
bounce_smooth(durations::NORMAL)
}
pub fn spring() -> Animation {
spring_animation(durations::SLOW)
}
pub fn spring_quick() -> Animation {
spring_animation(durations::NORMAL)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum AnimationState {
#[default]
Idle,
Running,
Complete,
}
impl AnimationState {
pub fn is_idle(&self) -> bool {
matches!(self, Self::Idle)
}
pub fn is_running(&self) -> bool {
matches!(self, Self::Running)
}
pub fn is_complete(&self) -> bool {
matches!(self, Self::Complete)
}
}
pub fn pulse_scale(delta: f32, min_scale: f32, max_scale: f32) -> f32 {
let oscillation = (delta * std::f32::consts::PI * 2.0).sin();
let normalized = (oscillation + 1.0) / 2.0; min_scale + (max_scale - min_scale) * normalized
}
pub fn pulse_opacity(delta: f32, min_opacity: f32, max_opacity: f32) -> f32 {
let oscillation = (delta * std::f32::consts::PI * 2.0).sin();
let normalized = (oscillation + 1.0) / 2.0;
min_opacity + (max_opacity - min_opacity) * normalized
}
pub fn shake_offset(delta: f32, max_offset: f32) -> f32 {
let frequency = 4.0;
let decay = 1.0 - delta;
(delta * std::f32::consts::PI * frequency).sin() * max_offset * decay
}
pub fn spring_bounce(delta: f32, amplitude: f32) -> f32 {
let damping = 0.7;
let frequency = 1.5;
let decay = (-damping * delta * 10.0).exp();
let oscillation = (frequency * delta * std::f32::consts::PI * 2.0).sin();
amplitude * decay * oscillation
}
pub fn lerp_f32(from: f32, to: f32, t: f32) -> f32 {
from + (to - from) * t.clamp(0.0, 1.0)
}
pub fn lerp_pixels(from: Pixels, to: Pixels, t: f32) -> Pixels {
let t = t.clamp(0.0, 1.0);
px(f32::from(from) + (f32::from(to) - f32::from(from)) * t)
}
pub fn lerp_color(from: Hsla, to: Hsla, t: f32) -> Hsla {
let t = t.clamp(0.0, 1.0);
Hsla {
h: from.h + (to.h - from.h) * t,
s: from.s + (to.s - from.s) * t,
l: from.l + (to.l - from.l) * t,
a: from.a + (to.a - from.a) * t,
}
}
pub fn lerp_shadow(from: &BoxShadow, to: &BoxShadow, t: f32) -> BoxShadow {
let t = t.clamp(0.0, 1.0);
BoxShadow {
color: lerp_color(from.color, to.color, t),
offset: point(
lerp_pixels(from.offset.x, to.offset.x, t),
lerp_pixels(from.offset.y, to.offset.y, t),
),
blur_radius: lerp_pixels(from.blur_radius, to.blur_radius, t),
spread_radius: lerp_pixels(from.spread_radius, to.spread_radius, t),
inset: false,
}
}
pub fn lerp_shadows(from: &[BoxShadow], to: &[BoxShadow], t: f32) -> SmallVec<[BoxShadow; 2]> {
let max_len = from.len().max(to.len());
let mut result = SmallVec::new();
let empty = BoxShadow {
color: hsla(0.0, 0.0, 0.0, 0.0),
offset: point(px(0.0), px(0.0)),
blur_radius: px(0.0),
spread_radius: px(0.0),
inset: false,
};
for i in 0..max_len {
let f = from.get(i).unwrap_or(&empty);
let t_shadow = to.get(i).unwrap_or(&empty);
result.push(lerp_shadow(f, t_shadow, t));
}
result
}