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 saturating_sub(self, rhs: Self) -> Self {
55        Self(self.0.saturating_sub(rhs.0))
56    }
57
58    pub(crate) fn min(self, rhs: Self) -> Self {
59        Self(self.0.min(rhs.0))
60    }
61
62    pub(crate) fn max(self, rhs: Self) -> Self {
63        Self(self.0.max(rhs.0))
64    }
65}
66
67impl AddAssign for Duration {
68    fn add_assign(&mut self, rhs: Self) {
69        self.0 += rhs.0;
70    }
71}
72
73impl Add for Duration {
74    type Output = Self;
75
76    fn add(self, rhs: Self) -> Self::Output {
77        Self(self.0 + rhs.0)
78    }
79}
80
81impl From<StdDuration> for Duration {
82    fn from(value: StdDuration) -> Self {
83        Self(value)
84    }
85}
86
87/// A non-negative animation start delay.
88#[derive(Debug, Clone, Copy, PartialEq, Default)]
89pub struct Delay(StdDuration);
90
91impl Delay {
92    /// No start delay.
93    pub const ZERO: Self = Self(StdDuration::ZERO);
94
95    /// Creates a delay from milliseconds.
96    #[must_use]
97    pub fn from_millis(millis: f64) -> Self {
98        Self(std_duration_from_secs(millis / 1000.0))
99    }
100
101    /// Creates a delay from seconds.
102    #[must_use]
103    pub fn from_secs(seconds: f64) -> Self {
104        Self(std_duration_from_secs(seconds))
105    }
106
107    /// Returns this delay in milliseconds.
108    #[must_use]
109    pub const fn as_millis(self) -> f64 {
110        self.0.as_secs_f64() * 1000.0
111    }
112}
113
114impl From<StdDuration> for Delay {
115    fn from(value: StdDuration) -> Self {
116        Self(value)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::{Delay, Duration};
123    use float_cmp::assert_approx_eq;
124
125    #[test]
126    fn duration_constructors_sanitize_negative_and_nan_values() {
127        assert!(Duration::from_millis(-10.0).is_zero());
128        assert!(Duration::from_secs(f64::NAN).is_zero());
129        assert!(Delay::from_millis(-10.0).as_millis().abs() <= f64::EPSILON);
130    }
131
132    #[test]
133    fn duration_reports_seconds_and_milliseconds() {
134        let duration = Duration::from_millis(1_250.0);
135
136        assert_approx_eq!(f64, duration.as_secs(), 1.25);
137        assert_approx_eq!(f64, duration.as_millis(), 1_250.0);
138    }
139
140    #[test]
141    fn duration_arithmetic_is_saturating_where_expected() {
142        let short = Duration::from_millis(25.0);
143        let long = Duration::from_millis(100.0);
144
145        assert_eq!(short.saturating_sub(long), Duration::ZERO);
146        assert_eq!(short.min(long), short);
147        assert_eq!(short.max(long), long);
148        assert_approx_eq!(f64, short.checked_mul(2).unwrap().as_millis(), 50.0);
149        assert_approx_eq!(
150            f64,
151            short
152                .checked_add_delay(Delay::from_millis(25.0))
153                .unwrap()
154                .as_millis(),
155            50.0
156        );
157    }
158}