Skip to main content

motion_core/time/
offset.rs

1//! Signed time displacements for places where moving earlier is meaningful.
2
3use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
4
5use super::{Duration, TICKS_PER_SECOND, try_rounded_ticks};
6
7/// A signed span of time, stored as integer flicks.
8///
9/// Unlike [`Duration`], an offset may be negative. It is the result of
10/// `Time - Time` and the type of clip offsets and similar shifts. Arithmetic
11/// panics on overflow, independently of the build profile.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct TimeOffset(i64);
14
15impl TimeOffset {
16    /// The empty offset.
17    pub const ZERO: Self = Self(0);
18
19    /// Constructs an offset without losing integer tick precision.
20    pub const fn from_ticks(ticks: i64) -> Self {
21        Self(ticks)
22    }
23
24    /// Rounds seconds to the nearest tick; halfway cases round away from zero.
25    ///
26    /// # Panics
27    /// Panics for nonfinite input or a rounded result outside the signed range;
28    /// see [`Self::try_from_seconds`].
29    pub fn from_seconds(seconds: f64) -> Self {
30        Self::try_from_seconds(seconds).unwrap_or_else(|error| panic!("{}", error.message))
31    }
32
33    /// Rounds seconds like [`Self::from_seconds`], reporting nonfinite or out-of-range input.
34    pub fn try_from_seconds(seconds: f64) -> Result<Self, crate::ValidationError> {
35        try_rounded_ticks(seconds * TICKS_PER_SECOND as f64).map(Self)
36    }
37
38    /// Returns the exact signed tick count.
39    pub const fn ticks(self) -> i64 {
40        self.0
41    }
42
43    /// Converts to floating-point seconds for animation mathematics.
44    pub fn as_seconds(self) -> f64 {
45        self.0 as f64 / TICKS_PER_SECOND as f64
46    }
47
48    /// Returns true when the offset moves toward earlier time.
49    pub const fn is_negative(self) -> bool {
50        self.0 < 0
51    }
52
53    /// Returns the length of the offset without its sign.
54    pub const fn magnitude(self) -> Duration {
55        Duration::from_ticks(self.0.unsigned_abs())
56    }
57
58    /// Returns the offset as a duration, or None when it is negative.
59    pub const fn to_duration(self) -> Option<Duration> {
60        if self.0 < 0 {
61            None
62        } else {
63            Some(Duration::from_ticks(self.0 as u64))
64        }
65    }
66}
67
68impl Add for TimeOffset {
69    type Output = Self;
70
71    fn add(self, other: Self) -> Self {
72        Self(
73            self.0
74                .checked_add(other.0)
75                .expect("offset addition overflow"),
76        )
77    }
78}
79
80impl Sub for TimeOffset {
81    type Output = Self;
82
83    fn sub(self, other: Self) -> Self {
84        Self(
85            self.0
86                .checked_sub(other.0)
87                .expect("offset subtraction overflow"),
88        )
89    }
90}
91
92impl AddAssign for TimeOffset {
93    fn add_assign(&mut self, other: Self) {
94        *self = *self + other;
95    }
96}
97
98impl SubAssign for TimeOffset {
99    fn sub_assign(&mut self, other: Self) {
100        *self = *self - other;
101    }
102}
103
104impl Neg for TimeOffset {
105    type Output = Self;
106
107    fn neg(self) -> Self {
108        Self(self.0.checked_neg().expect("offset negation overflow"))
109    }
110}