1use chrono::Utc;
2
3const SEC: f32 = 1_000.0;
4
5#[derive(Default, Debug)]
6pub struct Animation {
7 start: f32,
8 span: f32,
9 duration: f32,
10 stamp: i64,
11}
12
13impl Animation {
14 pub fn new(start: impl Into<f32>, end: impl Into<f32>, duration: impl Into<f32>) -> Self {
15 let start = start.into() * SEC;
16 let end = end.into() * SEC;
17 let span = end - start;
18 assert!(span != 0.0);
19 Self {
20 start,
21 span,
22 duration: duration.into() * SEC,
23 stamp: Utc::now().timestamp_millis(),
24 }
25 }
26
27 #[allow(clippy::cast_possible_truncation)]
28 pub fn finished(&self) -> bool {
29 Utc::now().timestamp_millis() >= self.stamp + self.duration as i64
30 }
31
32 #[allow(clippy::cast_possible_truncation)]
33 #[allow(clippy::cast_sign_loss)]
34 #[allow(clippy::cast_precision_loss)]
35 pub fn value(&self) -> f32 {
36 let now = Utc::now().timestamp_millis();
37 let delta = (now - self.stamp) as f32;
38 let passed = (delta / self.duration) as u64;
39 let even = passed % 2 == 0;
40 let passed = passed as f32;
41 let delta = delta - (passed * self.duration);
42 let ratio = delta / (self.duration);
43 let span = if even {
44 self.span * ratio
45 } else {
46 self.span - self.span * ratio
47 };
48 (self.start + span) / SEC
49 }
50}
51
52#[cfg(test)]
53mod test {
54 use crate::{Animation, sleep};
55
56 #[test]
57 fn test() {
58 let anim = Animation::new(0.0, 1.0, 0.5);
59
60 assert_eq!(anim.finished(), false);
61 assert_eq!(anim.value(), 0.0);
62
63 sleep(0.25);
64
65 assert_eq!(anim.finished(), false);
66 assert!(anim.value() >= 0.48 && anim.value() <= 0.52);
67
68 sleep(0.10);
69
70 assert_eq!(anim.finished(), false);
71 assert!(anim.value() >= 0.70 && anim.value() <= 0.725);
72
73 sleep(0.15);
74
75 assert_eq!(anim.finished(), true);
76 assert!(anim.value() >= 0.96 && anim.value() <= 1.04);
77
78 sleep(0.25);
79
80 assert_eq!(anim.finished(), true);
81 assert!(anim.value() >= 0.40 && anim.value() <= 0.60);
82 }
83}