use crate::traits::{AnimationTime, FloatRepresentable, Interpolable};
#[derive(Clone, Debug, Default)]
pub struct Animated<T, Time>
where
T: FloatRepresentable + Clone + Copy + PartialEq,
Time: AnimationTime,
{
animation: Animation<Time>,
pub value: T,
last_value: T,
}
impl<T, Time> Animated<T, Time>
where
T: FloatRepresentable + Clone + Copy + PartialEq,
Time: AnimationTime,
{
pub fn new_with_settings(value: T, duration_ms: f32, easing: Easing) -> Self {
let mut animation = Animation::default(value.float_value());
animation.settings.duration_ms = duration_ms;
animation.settings.easing = easing;
Animated {
value,
last_value: value,
animation,
}
}
pub fn new(value: T) -> Self {
Self {
value,
last_value: value,
animation: Animation::default(value.float_value()),
}
}
pub fn duration(mut self, duration_ms: f32) -> Self {
self.animation.settings.duration_ms = duration_ms;
self
}
pub fn easing(mut self, easing: Easing) -> Self {
self.animation.settings.easing = easing;
self
}
pub fn delay(mut self, delay_ms: f32) -> Self {
self.animation.delay_ms = delay_ms;
self
}
pub fn repeat(mut self, count: u32) -> Self {
self.animation.repetitions = count;
self
}
pub fn repeat_forever(mut self) -> Self {
self.animation.repeat_forever = true;
self
}
pub fn auto_reverse(mut self) -> Self {
self.animation.auto_reverse_repetitions = true;
self
}
pub fn auto_start(mut self, new_value: T, at: Time) -> Self {
self.transition(new_value, at);
self
}
pub fn asymmetric_duration(mut self, duration_ms: f32) -> Self {
self.animation.asymmetric_settings = Some(AnimationSettings {
duration_ms,
easing: self
.animation
.asymmetric_settings
.map(|a| a.easing)
.unwrap_or(self.animation.settings.easing),
});
self
}
pub fn asymmetric_easing(mut self, easing: Easing) -> Self {
self.animation.asymmetric_settings = Some(AnimationSettings {
duration_ms: self
.animation
.asymmetric_settings
.map(|a| a.duration_ms)
.unwrap_or(self.animation.settings.duration_ms),
easing,
});
self
}
pub fn transition(&mut self, new_value: T, at: Time) {
self.transition_internal(new_value, at, false);
}
pub fn transition_instantaneous(&mut self, new_value: T, at: Time) {
self.transition_internal(new_value, at, true);
}
fn transition_internal(&mut self, new_value: T, at: Time, instantaneous: bool) {
if self.value != new_value {
self.last_value = self.value;
self.value = new_value;
self.animation
.transition(new_value.float_value(), at, instantaneous)
}
}
pub fn in_progress(&self, time: Time) -> bool {
self.animation.in_progress(time)
}
pub fn animate<I>(&self, map: impl Fn(T) -> I, time: Time) -> I
where
I: Interpolable,
{
let interrupted_range = self.value.float_value() - self.last_value.float_value();
let unit_interrupt_value = if interrupted_range == 0. {
0.
} else {
(self.animation.origin - self.last_value.float_value()) / interrupted_range
};
let interrupt_interpolable =
map(self.last_value).interpolated(map(self.value), unit_interrupt_value);
interrupt_interpolable
.interpolated(map(self.value), self.animation.eased_unit_progress(time))
}
#[allow(dead_code)]
fn linear_progress(&self, time: Time) -> f32 {
self.animation.linear_progress(time)
}
#[allow(dead_code)]
fn eased_progress(&self, time: Time) -> f32 {
self.animation.eased_progress(time)
}
}
impl<T, Time> Animated<T, Time>
where
T: FloatRepresentable + Clone + Copy + PartialEq,
Time: AnimationTime,
{
pub fn animate_if_eq<I>(&self, value: T, equal: I, default: I, time: Time) -> I
where
I: Interpolable + Clone,
{
self.animate(
|v| {
if v == value {
equal.clone()
} else {
default.clone()
}
},
time,
)
}
}
impl<T, Time> Animated<T, Time>
where
T: FloatRepresentable + Clone + Copy + PartialEq,
T: Interpolable,
Time: AnimationTime,
{
pub fn animate_wrapped(&self, time: Time) -> T {
self.animate(|v| v, time)
}
}
impl<Time> Animated<bool, Time>
where
Time: AnimationTime,
{
pub fn animate_bool<I>(&self, false_value: I, true_value: I, time: Time) -> I
where
I: Interpolable + Clone,
{
self.animate(
move |b| {
if b {
true_value.clone()
} else {
false_value.clone()
}
},
time,
)
}
}
#[derive(Clone, Copy, Debug, Default)]
struct Animation<Time>
where
Time: AnimationTime,
{
origin: f32,
destination: f32,
delay_ms: f32,
settings: AnimationSettings,
asymmetric_settings: Option<AnimationSettings>,
repetitions: u32,
auto_reverse_repetitions: bool,
repeat_forever: bool,
transition_time: Option<Time>,
}
#[derive(Clone, Copy, Debug, Default)]
struct AnimationSettings {
duration_ms: f32,
easing: Easing,
}
impl<Time> Animation<Time>
where
Time: AnimationTime,
{
fn default(origin: f32) -> Self {
Animation {
origin,
destination: origin,
settings: AnimationSettings {
duration_ms: 100.,
easing: Easing::EaseInOut,
},
asymmetric_settings: None,
delay_ms: 0.,
repetitions: 1,
auto_reverse_repetitions: false,
repeat_forever: false,
transition_time: None,
}
}
fn transition(&mut self, destination: f32, time: Time, instantaneous: bool) {
if self.destination != destination {
if instantaneous {
self.origin = destination;
self.destination = destination;
return;
}
if self.in_progress(time) {
let eased_progress = self.eased_progress(time);
self.origin = eased_progress;
} else {
self.origin = self.destination;
}
self.transition_time = Some(time);
self.destination = destination;
}
}
fn current_progress(&self, time: Time) -> Progress {
let Some(transition_time) = self.transition_time else {
return Progress {
linear_unit_progress: 0.,
eased_unit_progress: 0.,
complete: true,
};
};
let elapsed = f32::max(0., time.elapsed_since(transition_time) - self.delay_ms);
let settings;
let elapsed_current;
let auto_reversing;
if self.auto_reverse_repetitions {
let asymmetry = self.asymmetric_settings.unwrap_or(self.settings);
let combined_durations = self.settings.duration_ms + asymmetry.duration_ms;
let first_animation = elapsed % combined_durations - self.settings.duration_ms < 0.;
if first_animation {
elapsed_current = elapsed % combined_durations;
settings = self.settings;
auto_reversing = false;
} else {
settings = asymmetry;
elapsed_current = elapsed % combined_durations - self.settings.duration_ms;
auto_reversing = true;
}
} else if self.destination < self.origin {
settings = self.asymmetric_settings.unwrap_or(self.settings);
elapsed_current = elapsed;
auto_reversing = false;
} else {
settings = self.settings;
elapsed_current = elapsed;
auto_reversing = false;
}
let total_duration = self.total_duration();
if total_duration == 0. {
return Progress {
linear_unit_progress: 1.,
eased_unit_progress: settings.easing.value(1.),
complete: true,
};
}
let complete = !self.repeat_forever && elapsed >= total_duration;
let repeat = elapsed_current / settings.duration_ms;
let progress = if complete { 1. } else { repeat % 1. };
if auto_reversing && !complete {
Progress {
linear_unit_progress: 1. - progress,
eased_unit_progress: settings.easing.value(1. - progress),
complete,
}
} else {
Progress {
linear_unit_progress: progress,
eased_unit_progress: settings.easing.value(progress),
complete,
}
}
}
fn linear_unit_progress(&self, time: Time) -> f32 {
self.current_progress(time).linear_unit_progress
}
fn eased_unit_progress(&self, time: Time) -> f32 {
self.current_progress(time).eased_unit_progress
}
fn total_duration(&self) -> f32 {
let true_repetitions = if self.auto_reverse_repetitions {
(self.repetitions * 2) + 1
} else {
self.repetitions
} as f32;
if true_repetitions > 1. {
if true_repetitions % 2. == 0. {
self.settings.duration_ms * (true_repetitions * 0.5)
+ self
.asymmetric_settings
.unwrap_or(self.settings)
.duration_ms
* (true_repetitions * 0.5)
} else {
self.settings.duration_ms * ((true_repetitions - true_repetitions % 2.) * 0.5)
+ self
.asymmetric_settings
.unwrap_or(self.settings)
.duration_ms
* ((true_repetitions - true_repetitions % 2.) * 0.5)
+ self.settings.duration_ms
}
} else if self.destination < self.origin {
self.asymmetric_settings
.unwrap_or(self.settings)
.duration_ms
* true_repetitions
} else {
self.settings.duration_ms * true_repetitions
}
}
fn linear_progress(&self, time: Time) -> f32 {
self.origin + (self.linear_unit_progress(time) * self.progress_range())
}
fn eased_progress(&self, time: Time) -> f32 {
self.origin + (self.eased_unit_progress(time) * self.progress_range())
}
fn progress_range(&self) -> f32 {
self.destination - self.origin
}
fn in_progress(&self, time: Time) -> bool {
!self.current_progress(time).complete
}
}
struct Progress {
linear_unit_progress: f32,
eased_unit_progress: f32,
complete: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum Easing {
#[default]
Linear,
EaseIn,
EaseOut,
EaseInOut,
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseInCubic,
EaseOutCubic,
EaseInOutCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
Custom(fn(f32) -> f32),
}
impl Easing {
pub fn value(self, x: f32) -> f32 {
const PI: f32 = std::f32::consts::PI;
match self {
Easing::Linear => x,
Easing::EaseIn => 1.0 - f32::cos((x * PI) / 2.0),
Easing::EaseOut => f32::sin((x * PI) / 2.0),
Easing::EaseInOut => -(f32::cos(PI * x) - 1.0) / 2.0,
Easing::EaseInQuad => x.powi(2),
Easing::EaseOutQuad => 1.0 - (1.0 - x) * (1.0 - x),
Easing::EaseInOutQuad => {
if x < 0.5 {
2.0 * x.powi(2)
} else {
1.0 - (-2.0 * x + 2.0).powi(2) / 2.0
}
}
Easing::EaseInCubic => x.powi(3),
Easing::EaseOutCubic => 1.0 - (1.0 - x).powi(3),
Easing::EaseInOutCubic => {
if x < 0.5 {
4.0 * x.powi(3)
} else {
1.0 - (-2.0 * x + 2.0).powi(3) / 2.0
}
}
Easing::EaseInQuart => x.powi(4),
Easing::EaseOutQuart => 1.0 - (1.0 - x).powi(4),
Easing::EaseInOutQuart => {
if x < 0.5 {
8.0 * x.powi(4)
} else {
1.0 - (-2.0 * x + 2.0).powi(4) / 2.0
}
}
Easing::EaseInQuint => x.powi(5),
Easing::EaseOutQuint => 1.0 - (1.0 - x).powi(5),
Easing::EaseInOutQuint => {
if x < 0.5 {
16.0 * x.powi(5)
} else {
1.0 - (-2.0 * x + 2.0).powi(5) / 2.0
}
}
Easing::EaseInExpo => {
if x == 0.0 {
0.0
} else {
(2.0_f32).powf(10.0 * x - 10.0)
}
}
Easing::EaseOutExpo => {
if x == 1.0 {
1.0
} else {
1.0 - (2.0_f32).powf(-10.0 * x)
}
}
Easing::EaseInOutExpo => match x {
0.0 => 0.0,
1.0 => 1.0,
x if x < 0.5 => (2.0_f32).powf(20.0 * x - 10.0) / 2.0,
_ => (2.0 - (2.0_f32).powf(-20.0 * x + 10.0)) / 2.0,
},
Easing::EaseInCirc => 1.0 - (1.0 - x.powi(2)).sqrt(),
Easing::EaseOutCirc => (1.0 - (x - 1.0).powi(2)).sqrt(),
Easing::EaseInOutCirc => {
if x < 0.5 {
(1.0 - (1.0 - (2.0 * x).powi(2)).sqrt()) / 2.0
} else {
(1.0 + (1.0 - (-2.0 * x + 2.0).powi(2)).sqrt()) / 2.0
}
}
Easing::EaseInBack => {
const C1: f32 = 1.70158;
const C3: f32 = C1 + 1.0;
C3 * x.powi(3) - C1 * x.powi(2)
}
Easing::EaseOutBack => {
const C1: f32 = 1.70158;
const C3: f32 = C1 + 1.0;
1.0 + C3 * (x - 1.0).powi(3) + C1 * (x - 1.0).powi(2)
}
Easing::EaseInOutBack => {
const C1: f32 = 1.70158;
const C2: f32 = C1 * 1.525;
if x < 0.5 {
((2.0 * x).powi(2) * ((C2 + 1.0) * 2.0 * x - C2)) / 2.0
} else {
((2.0 * x - 2.0).powi(2) * ((C2 + 1.0) * (x * 2.0 - 2.0) + C2) + 2.0) / 2.0
}
}
Easing::EaseInElastic => {
const C4: f32 = (2.0 * PI) / 3.0;
if x == 0.0 {
0.0
} else if x == 1.0 {
1.0
} else {
-(2.0_f32.powf(10.0 * x - 10.0)) * f32::sin((x * 10.0 - 10.75) * C4)
}
}
Easing::EaseOutElastic => {
const C4: f32 = (2.0 * PI) / 3.0;
if x == 0.0 {
0.0
} else if x == 1.0 {
1.0
} else {
2.0_f32.powf(-10.0 * x) * f32::sin((x * 10.0 - 0.75) * C4) + 1.0
}
}
Easing::EaseInOutElastic => {
const C5: f32 = (2.0 * PI) / 4.5;
if x == 0.0 {
0.0
} else if x == 1.0 {
1.0
} else if x < 0.5 {
-(2.0_f32.powf(20.0 * x - 10.0) * f32::sin((20.0 * x - 11.125) * C5)) / 2.0
} else {
(2.0_f32.powf(-20.0 * x + 10.0) * f32::sin((20.0 * x - 11.125) * C5)) / 2.0
+ 1.0
}
}
Easing::EaseInBounce => 1.0 - Self::EaseOutBounce.value(1.0 - x),
Easing::EaseOutBounce => {
const N1: f32 = 7.5625;
const D1: f32 = 2.75;
if x < 1.0 / D1 {
N1 * x.powi(2)
} else if x < 2.0 / D1 {
N1 * (x - 1.5 / D1).powi(2) + 0.75
} else if x < 2.5 / D1 {
N1 * (x - 2.25 / D1).powi(2) + 0.9375
} else {
N1 * (x - 2.625 / D1).powi(2) + 0.984375
}
}
Easing::EaseInOutBounce => {
if x < 0.5 {
(1.0 - Self::EaseOutBounce.value(1.0 - 2.0 * x)) / 2.0
} else {
(1.0 + Self::EaseOutBounce.value(2.0 * x - 1.0)) / 2.0
}
}
Easing::Custom(f) => f(x),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_repeat_forever() {
let mut anim = Animated::new(0.)
.duration(1000.)
.easing(Easing::Linear)
.repeat_forever();
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0);
assert_eq!(anim.animate_wrapped(1000.0), 0.0);
assert_eq!(anim.animate_wrapped(1500.0), 5.0);
assert_eq!(anim.animate_wrapped(2000.0), 0.0);
assert_eq!(anim.animate_wrapped(2500.0), 5.0);
assert!(anim.in_progress(10000.0));
}
fn plot_easing(easing: Easing) {
const WIDTH: usize = 80;
const HEIGHT: usize = 40;
let mut plot = vec![vec![' '; WIDTH]; HEIGHT];
for x in 0..WIDTH {
let t = x as f32 / (WIDTH - 1) as f32;
let y = easing.value(t);
let y_scaled = ((1.0 - y) * (HEIGHT - 20) as f32).round() as usize + 10;
let y_scaled = y_scaled.min(HEIGHT - 1);
plot[y_scaled][x] = '*';
}
println!("\nPlot for {:?}:", easing);
for row in plot {
println!("{}", row.iter().collect::<String>());
}
println!();
}
#[test]
fn visualize_all_easings() {
let easings = [
Easing::Linear,
Easing::EaseIn,
Easing::EaseOut,
Easing::EaseInOut,
Easing::EaseInQuad,
Easing::EaseOutQuad,
Easing::EaseInOutQuad,
Easing::EaseInCubic,
Easing::EaseOutCubic,
Easing::EaseInOutCubic,
Easing::EaseInQuart,
Easing::EaseOutQuart,
Easing::EaseInOutQuart,
Easing::EaseInQuint,
Easing::EaseOutQuint,
Easing::EaseInOutQuint,
Easing::EaseInExpo,
Easing::EaseOutExpo,
Easing::EaseInOutExpo,
Easing::EaseInCirc,
Easing::EaseOutCirc,
Easing::EaseInOutCirc,
Easing::EaseInBack,
Easing::EaseOutBack,
Easing::EaseInOutBack,
Easing::EaseInElastic,
Easing::EaseOutElastic,
Easing::EaseInOutElastic,
Easing::EaseInBounce,
Easing::EaseOutBounce,
Easing::EaseInOutBounce,
];
for easing in &easings {
plot_easing(*easing);
}
}
#[test]
fn test_custom_easing() {
let custom_ease = Easing::Custom(|x| x.powi(2)); assert_eq!(custom_ease.value(0.0), 0.0);
assert_eq!(custom_ease.value(0.5), 0.25);
assert_eq!(custom_ease.value(1.0), 1.0);
}
#[test]
fn test_linear_progress() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0);
assert_eq!(anim.animate_wrapped(1000.0), 10.0);
assert_eq!(anim.animate_wrapped(1500.0), 10.0); }
#[test]
fn test_eased_progress_with_easing() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::EaseIn);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 0.0);
assert!(anim.animate_wrapped(500.0) < 5.0); assert_eq!(anim.animate_wrapped(1000.0), 10.0);
}
#[test]
fn test_in_progress() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::EaseIn);
assert!(!anim.in_progress(0.0));
anim.transition(10.0, 0.0);
assert!(anim.in_progress(0.0));
assert!(anim.in_progress(500.0));
assert!(!anim.in_progress(1000.0));
}
#[test]
fn test_repetitions() {
let mut anim = Animated::new(0.)
.duration(1000.)
.easing(Easing::Linear)
.repeat(3);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(1500.0), 5.0); assert_eq!(anim.animate_wrapped(3000.0), 10.0); assert_eq!(anim.animate_wrapped(3500.0), 10.0); assert!(!anim.in_progress(5001.));
}
#[test]
fn test_auto_reverse_repetitions() {
let mut anim = Animated::new(0.)
.duration(1000.)
.easing(Easing::Linear)
.auto_reverse()
.repeat(2);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0); assert_eq!(anim.animate_wrapped(1500.0), 5.0); assert_eq!(anim.animate_wrapped(2500.0), 5.0); assert_eq!(anim.animate_wrapped(3500.0), 5.0); assert_eq!(anim.animate_wrapped(4000.0), 0.0); assert!(!anim.in_progress(5000.));
}
#[test]
fn test_auto_reverse_repetitions_bool() {
let anim = Animated::new(false)
.duration(1000.)
.auto_start(true, 0.)
.repeat(2)
.auto_reverse();
let map = |b| if b { 1. } else { 0. };
anim.animate(map, 500.);
assert_eq!(anim.animate(map, 500.0), 0.5); assert_eq!(anim.animate(map, 1500.0), 0.5); assert_eq!(anim.animate(map, 2500.0), 0.5); assert_eq!(anim.animate(map, 3500.0), 0.5); assert!(anim.in_progress(3500.));
assert_eq!(anim.animate(map, 4000.0), 0.0); assert!(!anim.in_progress(5000.01));
}
#[test]
fn test_delay() {
let mut anim = Animated::new(0.)
.duration(1000.)
.easing(Easing::Linear)
.delay(500.);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(250.0), 0.0); assert_eq!(anim.animate_wrapped(750.0), 2.5); assert_eq!(anim.animate_wrapped(1500.0), 10.0); }
#[test]
fn test_interruption() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0);
anim.transition(20.0, 500.0); assert_eq!(anim.animate_wrapped(1000.0), 12.5); assert_eq!(anim.animate_wrapped(1500.0), 20.0); }
#[test]
fn test_instant_animation() {
let mut anim = Animated::new(0.).duration(0.).easing(Easing::Linear);
assert_eq!(anim.animate_wrapped(0.0), 0.0);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 10.0);
}
#[test]
fn test_progression() {
let mut anim = Animated::new(0.).duration(1.).easing(Easing::Linear);
anim.transition(10.0, 0.5);
assert_eq!(anim.value, 10.);
assert_eq!(anim.animate_wrapped(1.0), 5.0);
assert_eq!(anim.animate_wrapped(1.5), 10.0);
anim.transition(0.0, 1.5);
assert_eq!(anim.value, 0.);
assert_eq!(anim.linear_progress(2.5), 0.0);
anim.transition(10.0, 3.);
assert_eq!(anim.value, 10.);
assert!(approximately_equal(anim.animate_wrapped(3.), 0.0));
assert!(approximately_equal(anim.animate_wrapped(3.2), 2.0));
assert!(approximately_equal(anim.animate_wrapped(3.8), 8.0));
assert!(approximately_equal(anim.animate_wrapped(4.0), 10.0));
}
#[test]
fn test_progression_negative() {
let mut anim = Animated::new(0.).duration(1.).easing(Easing::EaseInOut);
anim.transition(-10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.5), -5.0);
assert_eq!(anim.animate_wrapped(1.0), -10.0);
assert!(anim.eased_progress(0.25) > anim.linear_progress(0.25));
assert!(anim.eased_progress(0.5) == anim.linear_progress(0.5));
assert!(anim.eased_progress(0.75) < anim.linear_progress(0.75));
anim.transition(0.0, 1.0);
assert_eq!(anim.animate_wrapped(1.5), -5.0);
assert_eq!(anim.animate_wrapped(2.0), 0.0);
}
#[test]
fn test_multiple_interrupts_start_forward() {
let mut anim = Animated::new(0.).duration(1.).easing(Easing::EaseInOut);
anim.transition(1.0, 0.);
assert!(anim.in_progress(0.5));
let progress_at_interrupt = anim.eased_progress(0.5);
assert_eq!(progress_at_interrupt, Easing::EaseInOut.value(0.5));
anim.transition(0.0, 0.5);
assert_eq!(anim.eased_progress(0.5), progress_at_interrupt);
assert!(anim.in_progress(0.7));
anim.transition(1.0, 0.7);
assert!(anim.in_progress(0.9));
}
#[test]
fn test_asymmetric() {
let mut anim = Animated::new(0.)
.duration(1000.)
.easing(Easing::Linear)
.asymmetric_duration(2000.)
.asymmetric_easing(Easing::EaseInOut);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0); assert_eq!(anim.animate_wrapped(1000.0), 10.);
anim.transition(0.0, 1000.0);
assert!(anim.animate_wrapped(1500.0) > 7.5); assert_eq!(anim.animate_wrapped(2000.0), 5.0); assert!(anim.animate_wrapped(2500.0) < 2.5); assert_eq!(anim.animate_wrapped(3000.0), 0.0);
anim.transition(10.0, 3000.0);
assert_eq!(anim.animate_wrapped(3250.0), 2.5); assert_eq!(anim.animate_wrapped(3500.0), 5.0); assert_eq!(anim.animate_wrapped(3750.0), 7.5); assert_eq!(anim.animate_wrapped(4000.0), 10.0); }
#[test]
fn test_asymmetric_auto_reversal() {
let mut anim = Animated::new(0.)
.duration(1000.)
.easing(Easing::Linear)
.asymmetric_duration(2000.)
.auto_reverse()
.repeat(1);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0); assert_eq!(anim.animate_wrapped(1000.0), 10.);
assert_eq!(anim.animate_wrapped(1500.0), 7.5); assert_eq!(anim.animate_wrapped(2000.0), 5.0); assert_eq!(anim.animate_wrapped(2500.0), 2.5); assert_eq!(anim.animate_wrapped(3000.0), 0.0);
assert_eq!(anim.animate_wrapped(3250.0), 2.5); assert_eq!(anim.animate_wrapped(3500.0), 5.0); assert_eq!(anim.animate_wrapped(3750.0), 7.5); assert_eq!(anim.animate_wrapped(4000.0), 10.0);
assert!(anim.eased_progress(3250.0) == anim.linear_progress(3250.0)); assert!(anim.eased_progress(3500.0) == anim.linear_progress(3500.0)); assert!(anim.eased_progress(3750.0) == anim.linear_progress(3750.0)); }
#[test]
fn test_auto_reversal() {
let mut anim = Animated::new(0.)
.duration(1000.)
.auto_reverse()
.repeat(1)
.easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 0.0);
assert_eq!(anim.animate_wrapped(250.0), 2.5); assert_eq!(anim.animate_wrapped(500.0), 5.0); assert_eq!(anim.animate_wrapped(750.0), 7.5); assert_eq!(anim.animate_wrapped(1000.0), 10.0);
assert_eq!(anim.animate_wrapped(1250.0), 7.5); assert_eq!(anim.animate_wrapped(1500.0), 5.0); assert_eq!(anim.animate_wrapped(1750.0), 2.5); assert_eq!(anim.animate_wrapped(2000.0), 0.0);
assert_eq!(anim.animate_wrapped(2250.0), 2.5); assert_eq!(anim.animate_wrapped(2500.0), 5.0); assert_eq!(anim.animate_wrapped(2750.0), 7.5); assert_eq!(anim.animate_wrapped(3000.0), 10.0); }
#[test]
fn test_negative_values() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(-10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 0.0);
assert_eq!(anim.animate_wrapped(500.0), -5.0);
assert_eq!(anim.animate_wrapped(1000.0), -10.0);
anim.transition(-5.0, 1000.0);
assert_eq!(anim.animate_wrapped(1000.0), -10.0);
assert_eq!(anim.animate_wrapped(1500.0), -7.5);
assert_eq!(anim.animate_wrapped(2000.0), -5.0);
}
#[test]
fn test_negative_to_positive_transition() {
let mut anim = Animated::new(-5.).duration(1000.).easing(Easing::Linear);
anim.transition(5.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), -5.0);
assert_eq!(anim.animate_wrapped(500.0), 0.0);
assert_eq!(anim.animate_wrapped(1000.0), 5.0);
}
#[test]
fn test_interruption_with_negative_values() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(-10.0, 0.0);
assert_eq!(anim.animate_wrapped(250.0), -2.5);
anim.transition(5.0, 250.0); assert_eq!(anim.animate_wrapped(750.0), 1.25); assert_eq!(anim.animate_wrapped(1250.0), 5.0); }
#[test]
fn test_multiple_interruptions() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.);
anim.transition(15.0, 500.0); assert_eq!(anim.animate_wrapped(1000.0), 10.);
anim.transition(0.0, 1000.0); assert!(approximately_equal(anim.animate_wrapped(1500.0), 5.)); assert_eq!(anim.animate_wrapped(2000.0), 0.0); }
#[test]
fn test_interrupt_unchanged_destination() {
let mut anim_a = Animated::new(0.).duration(1000.).easing(Easing::Linear);
let mut anim_b = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim_a.transition(10., 0.);
anim_b.transition(10., 0.);
anim_a.transition(10., 250.);
assert_eq!(anim_a.linear_progress(250.), anim_b.linear_progress(250.));
assert_eq!(anim_a.linear_progress(500.), anim_b.linear_progress(500.));
assert_eq!(anim_a.linear_progress(750.), anim_b.linear_progress(750.));
assert_eq!(anim_a.linear_progress(1000.), anim_b.linear_progress(1000.));
}
#[test]
fn test_interruption_with_direction_change() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(500.0), 5.0);
anim.transition(-5.0, 500.0); assert!(approximately_equal(anim.animate_wrapped(1000.0), 0.0)); assert_eq!(anim.animate_wrapped(1500.0), -5.0); }
#[test]
fn test_zero_duration_transition() {
let mut anim = Animated::new(0.).duration(0.).easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(0.0), 10.0); assert!(!anim.in_progress(0.0)); }
#[test]
fn test_interruption_at_completion() {
let mut anim = Animated::new(0.).duration(1000.).easing(Easing::Linear);
anim.transition(10.0, 0.0);
assert_eq!(anim.animate_wrapped(1000.0), 10.0); anim.transition(20.0, 1000.0); assert_eq!(anim.animate_wrapped(1500.0), 15.0); assert_eq!(anim.animate_wrapped(2000.0), 20.0); }
#[test]
fn test_animate() {
let mut anim = Animated::new(0.0f32)
.duration(1000.0)
.easing(Easing::Linear);
anim.transition(10.0, 0.0);
let result = anim.animate_wrapped(500.0);
assert_eq!(result, 5.0);
let result = anim.animate(|v| v * 2.0, 750.0);
assert_eq!(result, 15.0);
}
#[test]
fn test_animate_if_eq() {
let mut anim = Animated::new(0.0f32)
.duration(1000.0)
.easing(Easing::Linear);
anim.transition(10.0, 0.0);
let result = anim.animate_if_eq(10.0, 100.0, 0.0, 500.0);
assert_eq!(result, 50.0);
let result = anim.animate_if_eq(5.0, 100.0, 0.0, 500.0);
assert_eq!(result, 0.0);
}
#[test]
fn test_animate_bool() {
let mut anim = Animated::new(false).duration(1000.0).easing(Easing::Linear);
anim.transition(true, 0.0);
let result = anim.animate_bool(0.0, 10.0, 500.0);
assert_eq!(result, 5.0);
let result = anim.animate_bool(0.0, 10.0, 1000.0);
assert_eq!(result, 10.0);
}
#[test]
fn test_animate_with_interruption() {
let mut anim = Animated::new(0.0f32)
.duration(1000.0)
.easing(Easing::Linear);
anim.transition(10.0, 0.0);
let result = anim.animate_wrapped(500.0);
assert_eq!(result, 5.0);
anim.transition(20.0, 500.0);
let result = anim.animate(|v| v, 1000.0);
assert_eq!(result, 12.5);
let result = anim.animate_wrapped(1500.0);
assert_eq!(result, 20.0);
}
#[test]
fn test_animate_with_custom_easing() {
let custom_ease = Easing::Custom(|x| x.powi(2)); let mut anim = Animated::new(0.0f32).duration(1000.0).easing(custom_ease);
anim.transition(10.0, 0.0);
let result = anim.animate(|v| v, 500.0);
assert_eq!(result, 2.5);
let result = anim.animate(|v| v, 750.0);
assert_eq!(result, 5.625); }
#[test]
fn test_no_change_after_completion() {
let anim = Animated::new(false)
.duration(400.)
.auto_start(true, 0.)
.repeat(2)
.auto_reverse();
assert_eq!(anim.animate_bool(0., 10., 800.), 0.);
assert_eq!(anim.animate_bool(0., 10., 1000.), 5.);
assert_eq!(anim.animate_bool(0., 10., 1200.), 10.);
assert_eq!(anim.animate_bool(0., 10., 1400.), 5.);
assert_eq!(anim.animate_bool(0., 10., 1600.), 0.);
assert_eq!(anim.animate_bool(0., 10., 1800.), 5.);
assert_eq!(anim.animate_bool(0., 10., 2000.), 10.);
assert_eq!(anim.animate_bool(0., 10., 2200.), 10.);
assert_eq!(anim.animate_bool(0., 10., 2400.), 10.);
assert_eq!(anim.animate_bool(0., 10., 2600.), 10.);
assert_eq!(anim.animate_bool(0., 10., 2800.), 10.);
assert_eq!(anim.animate_bool(0., 10., 3000.), 10.);
}
fn approximately_equal(a: f32, b: f32) -> bool {
let close = f32::abs(a - b) < 1e-5;
if !close {
dbg!(a, b);
}
close
}
}