Skip to main content

aura_anim_core/timing/
duration.rs

1use std::{
2    ops::{Add, AddAssign},
3    time::Duration as StdDuration,
4};
5
6use crate::timing::utils::std_duration_from_secs;
7
8/// A non-negative animation duration.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
10pub struct Duration(StdDuration);
11
12impl Duration {
13    /// A zero-length duration.
14    pub const ZERO: Self = Self(StdDuration::ZERO);
15
16    /// Returns whether this duration is zero.
17    #[must_use]
18    pub fn is_zero(&self) -> bool {
19        self.0.is_zero()
20    }
21
22    /// Creates a duration from milliseconds.
23    #[must_use]
24    pub fn from_millis(millis: f64) -> Self {
25        Self(std_duration_from_secs(millis / 1000.0))
26    }
27
28    /// Creates a duration from seconds.
29    #[must_use]
30    pub fn from_secs(seconds: f64) -> Self {
31        Self(std_duration_from_secs(seconds))
32    }
33
34    /// Returns this duration in milliseconds.
35    #[must_use]
36    pub const fn as_millis(self) -> f64 {
37        self.0.as_secs_f64() * 1000.0
38    }
39
40    /// Returns this duration in seconds.
41    #[must_use]
42    pub const fn as_secs(self) -> f64 {
43        self.0.as_secs_f64()
44    }
45
46    pub(crate) fn checked_mul(self, rhs: u32) -> Option<Self> {
47        self.0.checked_mul(rhs).map(Self)
48    }
49
50    pub(crate) fn checked_add_delay(self, rhs: Delay) -> Option<Self> {
51        self.0.checked_add(rhs.0).map(Self)
52    }
53
54    pub(crate) fn checked_sub_delay(self, rhs: Delay) -> Option<Self> {
55        self.0.checked_sub(rhs.0).map(Self)
56    }
57
58    pub(crate) fn divided_by(self, divisor: f64) -> Self {
59        if divisor.is_finite() && divisor > 0.0 {
60            let seconds = self.0.as_secs_f64() / divisor;
61            if seconds.is_infinite() {
62                Self(StdDuration::MAX)
63            } else {
64                Self(std_duration_from_secs(seconds))
65            }
66        } else {
67            self
68        }
69    }
70
71    pub(crate) fn saturating_sub(self, rhs: Self) -> Self {
72        Self(self.0.saturating_sub(rhs.0))
73    }
74
75    pub(crate) fn min(self, rhs: Self) -> Self {
76        Self(self.0.min(rhs.0))
77    }
78
79    pub(crate) fn max(self, rhs: Self) -> Self {
80        Self(self.0.max(rhs.0))
81    }
82}
83
84impl AddAssign for Duration {
85    fn add_assign(&mut self, rhs: Self) {
86        self.0 += rhs.0;
87    }
88}
89
90impl Add for Duration {
91    type Output = Self;
92
93    fn add(self, rhs: Self) -> Self::Output {
94        Self(self.0 + rhs.0)
95    }
96}
97
98impl From<StdDuration> for Duration {
99    fn from(value: StdDuration) -> Self {
100        Self(value)
101    }
102}
103
104impl From<f32> for Duration {
105    fn from(value: f32) -> Self {
106        Duration::from_millis(f64::from(value))
107    }
108}
109
110impl From<f64> for Duration {
111    fn from(value: f64) -> Self {
112        Duration::from_millis(value)
113    }
114}
115
116impl From<i32> for Duration {
117    fn from(value: i32) -> Self {
118        Duration::from_millis(f64::from(value))
119    }
120}
121
122impl From<u32> for Duration {
123    fn from(value: u32) -> Self {
124        Self(StdDuration::from_millis(u64::from(value)))
125    }
126}
127
128impl From<u64> for Duration {
129    fn from(value: u64) -> Self {
130        Self(StdDuration::from_millis(value))
131    }
132}
133
134/// A non-negative animation start delay.
135#[derive(Debug, Clone, Copy, PartialEq, Default)]
136pub struct Delay(StdDuration);
137
138impl Delay {
139    /// No start delay.
140    pub const ZERO: Self = Self(StdDuration::ZERO);
141
142    /// Creates a delay from milliseconds.
143    #[must_use]
144    pub fn from_millis(millis: f64) -> Self {
145        Self(std_duration_from_secs(millis / 1000.0))
146    }
147
148    /// Creates a delay from seconds.
149    #[must_use]
150    pub fn from_secs(seconds: f64) -> Self {
151        Self(std_duration_from_secs(seconds))
152    }
153
154    /// Returns this delay in milliseconds.
155    #[must_use]
156    pub const fn as_millis(self) -> f64 {
157        self.0.as_secs_f64() * 1000.0
158    }
159}
160
161impl From<StdDuration> for Delay {
162    fn from(value: StdDuration) -> Self {
163        Self(value)
164    }
165}
166
167impl From<f32> for Delay {
168    fn from(value: f32) -> Self {
169        Delay::from_millis(f64::from(value))
170    }
171}
172
173impl From<f64> for Delay {
174    fn from(value: f64) -> Self {
175        Delay::from_millis(value)
176    }
177}
178
179impl From<i32> for Delay {
180    fn from(value: i32) -> Self {
181        Delay::from_millis(f64::from(value))
182    }
183}
184
185impl From<u32> for Delay {
186    fn from(value: u32) -> Self {
187        Self(StdDuration::from_millis(u64::from(value)))
188    }
189}
190
191impl From<u64> for Delay {
192    fn from(value: u64) -> Self {
193        Self(StdDuration::from_millis(value))
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::{Delay, Duration};
200    use float_cmp::assert_approx_eq;
201
202    #[test]
203    fn duration_constructors_sanitize_negative_and_nan_values() {
204        assert!(Duration::from_millis(-10.0).is_zero());
205        assert!(Duration::from_secs(f64::NAN).is_zero());
206        assert!(Delay::from_millis(-10.0).as_millis().abs() <= f64::EPSILON);
207    }
208
209    #[test]
210    fn duration_reports_seconds_and_milliseconds() {
211        let duration = Duration::from_millis(1_250.0);
212
213        assert_approx_eq!(f64, duration.as_secs(), 1.25);
214        assert_approx_eq!(f64, duration.as_millis(), 1_250.0);
215    }
216
217    #[test]
218    fn duration_arithmetic_is_saturating_where_expected() {
219        let short = Duration::from_millis(25.0);
220        let long = Duration::from_millis(100.0);
221
222        assert_eq!(short.saturating_sub(long), Duration::ZERO);
223        assert_eq!(short.min(long), short);
224        assert_eq!(short.max(long), long);
225        assert_approx_eq!(f64, short.checked_mul(2).unwrap().as_millis(), 50.0);
226        assert_approx_eq!(
227            f64,
228            short
229                .checked_add_delay(Delay::from_millis(25.0))
230                .unwrap()
231                .as_millis(),
232            50.0
233        );
234        assert_eq!(
235            long.checked_sub_delay(Delay::from_millis(25.0)),
236            Some(Duration::from_millis(75.0))
237        );
238        assert_eq!(short.checked_sub_delay(Delay::from_millis(50.0)), None);
239        assert_eq!(long.divided_by(2.0), Duration::from_millis(50.0));
240        assert_eq!(long.divided_by(0.5), Duration::from_millis(200.0));
241        assert_eq!(long.divided_by(0.0), long);
242        assert_eq!(long.divided_by(f64::NAN), long);
243        assert_eq!(
244            long.divided_by(f64::MIN_POSITIVE),
245            Duration(std::time::Duration::MAX)
246        );
247    }
248}