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
104/// A non-negative animation start delay.
105#[derive(Debug, Clone, Copy, PartialEq, Default)]
106pub struct Delay(StdDuration);
107
108impl Delay {
109    /// No start delay.
110    pub const ZERO: Self = Self(StdDuration::ZERO);
111
112    /// Creates a delay from milliseconds.
113    #[must_use]
114    pub fn from_millis(millis: f64) -> Self {
115        Self(std_duration_from_secs(millis / 1000.0))
116    }
117
118    /// Creates a delay from seconds.
119    #[must_use]
120    pub fn from_secs(seconds: f64) -> Self {
121        Self(std_duration_from_secs(seconds))
122    }
123
124    /// Returns this delay in milliseconds.
125    #[must_use]
126    pub const fn as_millis(self) -> f64 {
127        self.0.as_secs_f64() * 1000.0
128    }
129}
130
131impl From<StdDuration> for Delay {
132    fn from(value: StdDuration) -> Self {
133        Self(value)
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::{Delay, Duration};
140    use float_cmp::assert_approx_eq;
141
142    #[test]
143    fn duration_constructors_sanitize_negative_and_nan_values() {
144        assert!(Duration::from_millis(-10.0).is_zero());
145        assert!(Duration::from_secs(f64::NAN).is_zero());
146        assert!(Delay::from_millis(-10.0).as_millis().abs() <= f64::EPSILON);
147    }
148
149    #[test]
150    fn duration_reports_seconds_and_milliseconds() {
151        let duration = Duration::from_millis(1_250.0);
152
153        assert_approx_eq!(f64, duration.as_secs(), 1.25);
154        assert_approx_eq!(f64, duration.as_millis(), 1_250.0);
155    }
156
157    #[test]
158    fn duration_arithmetic_is_saturating_where_expected() {
159        let short = Duration::from_millis(25.0);
160        let long = Duration::from_millis(100.0);
161
162        assert_eq!(short.saturating_sub(long), Duration::ZERO);
163        assert_eq!(short.min(long), short);
164        assert_eq!(short.max(long), long);
165        assert_approx_eq!(f64, short.checked_mul(2).unwrap().as_millis(), 50.0);
166        assert_approx_eq!(
167            f64,
168            short
169                .checked_add_delay(Delay::from_millis(25.0))
170                .unwrap()
171                .as_millis(),
172            50.0
173        );
174        assert_eq!(
175            long.checked_sub_delay(Delay::from_millis(25.0)),
176            Some(Duration::from_millis(75.0))
177        );
178        assert_eq!(short.checked_sub_delay(Delay::from_millis(50.0)), None);
179        assert_eq!(long.divided_by(2.0), Duration::from_millis(50.0));
180        assert_eq!(long.divided_by(0.5), Duration::from_millis(200.0));
181        assert_eq!(long.divided_by(0.0), long);
182        assert_eq!(long.divided_by(f64::NAN), long);
183        assert_eq!(
184            long.divided_by(f64::MIN_POSITIVE),
185            Duration(std::time::Duration::MAX)
186        );
187    }
188}