1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
use crate::transition_item::TransitionItem;
use crate::Easing;
use std::time::{Duration, SystemTime};

/// A value that smoothly goes to the target during a specific time.
/// The target can be changed at any time. No jumps will occur.
/// It's expected that time is always increasing.
/// Every method receives `current_time` as a parameter to allow testing,
/// and have a consistent behavior during a single animation frame.
#[derive(Clone, Debug)]
pub struct InertialValue<T: TransitionItem> {
    target: T,
    start_time: SystemTime,
    duration: Duration,
    easing: Easing,
    parent: Option<Box<InertialValue<T>>>,
}

impl<T: TransitionItem> InertialValue<T> {
    /// Create a new inertial value at a specific time.
    pub fn new(value: T, start_time: SystemTime) -> Self {
        Self {
            target: value,
            start_time,
            duration: Duration::default(),
            easing: Easing::None,
            parent: None,
        }
    }

    /// Check if the inertial value reached the target.
    pub fn is_finished(&self, current_time: SystemTime) -> bool {
        current_time > self.start_time + self.duration
    }

    /// Get the target value.
    pub fn target(&self) -> T {
        self.target.clone()
    }

    /// Get transition end time.
    pub fn end_time(&self) -> SystemTime {
        self.start_time + self.duration
    }

    /// Get the value of the inertial value at a specific time.
    /// * `current_time` - The time to get the value of the inertial value, usually `SystemTime::now()`.
    pub fn get(&self, current_time: SystemTime) -> T {
        if current_time < self.start_time {
            if let Some(parent) = &self.parent {
                parent.get(current_time)
            } else {
                self.target.clone()
            }
        } else if self.is_finished(current_time) {
            self.target.clone()
        } else if let Some(parent) = &self.parent {
            let elapsed = current_time
                .duration_since(self.start_time)
                .unwrap_or_default();

            let t = elapsed.as_secs_f32() / self.duration.as_secs_f32();
            let t = self.easing.ease(t);

            parent.get(current_time).mix(self.target.clone(), t)
        } else {
            self.target.clone()
        }
    }

    /// Create child inertial value with a new target at a specific time.
    /// Easing is set to default (`QuadraticInOut`).
    /// * `target` - The new target value.
    /// * `current_time` - The time to start the transition, usually `SystemTime::now()`.
    /// * `duration` - The duration of the transition.
    pub fn go_to(self, target: T, current_time: SystemTime, duration: Duration) -> Self {
        self.ease_to(target, current_time, duration, Easing::default())
    }

    /// Create child inertial value with a new target, easing and start time.
    /// * `target` - The new target value.
    /// * `start_time` - The time to start the transition, usually `SystemTime::now()`.
    /// * `duration` - The duration of the transition.
    pub fn ease_to(
        self,
        target: T,
        current_time: SystemTime,
        duration: Duration,
        easing: Easing,
    ) -> Self {
        if target == self.target {
            self
        } else {
            Self {
                target,
                start_time: current_time,
                duration,
                easing,
                parent: self.clean_up_at(current_time),
            }
        }
    }

    /// Remove all finished ancestors.
    pub(self) fn clean_up_at(self, current_time: SystemTime) -> Option<Box<Self>> {
        let is_finished = self.is_finished(current_time);

        Some(Box::new(Self {
            target: self.target,
            start_time: self.start_time,
            duration: self.duration,
            easing: self.easing,
            parent: if is_finished {
                None
            } else {
                self.parent
                    .and_then(|parent| parent.clean_up_at(current_time))
            },
        }))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_at() {
        let start_time = SystemTime::now();
        let inertial_value = InertialValue::new(5, start_time);
        assert_eq!(inertial_value.get(start_time), 5);
        assert_eq!(inertial_value.get(start_time + Duration::from_secs(1)), 5);
    }

    #[test]
    fn go_to_at() {
        let start_time = SystemTime::now();
        let inertial_value = InertialValue::new(5.0, start_time);

        let new_start_time = start_time + Duration::from_millis(500);
        let new_duration = Duration::from_secs(1);
        let new_inertial_value = inertial_value.go_to(10.0, new_start_time, new_duration);

        assert_eq!(new_inertial_value.get(start_time), 5.0);
        assert_eq!(new_inertial_value.get(new_start_time), 5.0);
        assert_eq!(
            new_inertial_value.get(new_start_time + Duration::from_millis(500)),
            7.5
        );
        assert_eq!(new_inertial_value.get(new_start_time + new_duration), 10.0);
    }
}