Skip to main content

argui_animation/
timing.rs

1use crate::Duration;
2use std::fmt;
3
4#[derive(Clone, Copy, Debug, PartialEq)]
5pub enum Iterations {
6    Finite(f64),
7    Infinite,
8}
9
10impl Default for Iterations {
11    fn default() -> Self {
12        Self::Finite(1.0)
13    }
14}
15
16#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
17pub enum Direction {
18    #[default]
19    Normal,
20    Reverse,
21    Alternate,
22    AlternateReverse,
23}
24
25#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
26pub enum FillMode {
27    #[default]
28    None,
29    Forwards,
30    Backwards,
31    Both,
32}
33
34impl FillMode {
35    pub(crate) const fn fills_before(self) -> bool {
36        matches!(self, Self::Backwards | Self::Both)
37    }
38
39    pub(crate) const fn fills_after(self) -> bool {
40        matches!(self, Self::Forwards | Self::Both)
41    }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq)]
45pub struct Timing {
46    pub duration: Duration,
47    pub delay: Duration,
48    pub end_delay: Duration,
49    pub iterations: Iterations,
50    pub direction: Direction,
51    pub fill: FillMode,
52    pub playback_rate: f64,
53}
54
55impl Timing {
56    #[must_use]
57    pub const fn new(duration: Duration) -> Self {
58        Self {
59            duration,
60            delay: Duration::ZERO,
61            end_delay: Duration::ZERO,
62            iterations: Iterations::Finite(1.0),
63            direction: Direction::Normal,
64            fill: FillMode::None,
65            playback_rate: 1.0,
66        }
67    }
68
69    #[must_use]
70    pub const fn delay(mut self, delay: Duration) -> Self {
71        self.delay = delay;
72        self
73    }
74
75    #[must_use]
76    pub const fn end_delay(mut self, end_delay: Duration) -> Self {
77        self.end_delay = end_delay;
78        self
79    }
80
81    #[must_use]
82    pub const fn iterations(mut self, iterations: Iterations) -> Self {
83        self.iterations = iterations;
84        self
85    }
86
87    #[must_use]
88    pub const fn direction(mut self, direction: Direction) -> Self {
89        self.direction = direction;
90        self
91    }
92
93    #[must_use]
94    pub const fn fill(mut self, fill: FillMode) -> Self {
95        self.fill = fill;
96        self
97    }
98
99    #[must_use]
100    pub const fn playback_rate(mut self, playback_rate: f64) -> Self {
101        self.playback_rate = playback_rate;
102        self
103    }
104
105    pub(crate) fn validate(self) -> Result<Self, TimingError> {
106        let iterations_valid = match self.iterations {
107            Iterations::Finite(value) => value.is_finite() && value > 0.0,
108            Iterations::Infinite => true,
109        };
110        if self.duration == Duration::ZERO {
111            Err(TimingError::ZeroDuration)
112        } else if !iterations_valid {
113            Err(TimingError::InvalidIterations)
114        } else if !self.playback_rate.is_finite() || self.playback_rate == 0.0 {
115            Err(TimingError::InvalidPlaybackRate)
116        } else {
117            Ok(self)
118        }
119    }
120
121    pub(crate) fn active_seconds(self) -> f64 {
122        match self.iterations {
123            Iterations::Finite(iterations) => self.duration.as_secs_f64() * iterations,
124            Iterations::Infinite => f64::INFINITY,
125        }
126    }
127
128    pub(crate) fn total_seconds(self) -> f64 {
129        self.delay.as_secs_f64() + self.active_seconds() + self.end_delay.as_secs_f64()
130    }
131}
132
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub enum TimingError {
135    ZeroDuration,
136    InvalidIterations,
137    InvalidPlaybackRate,
138    InvalidKeyframes,
139}
140
141impl fmt::Display for TimingError {
142    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
143        match self {
144            Self::ZeroDuration => {
145                formatter.write_str("animation duration must be greater than zero")
146            }
147            Self::InvalidIterations => {
148                formatter.write_str("animation iterations must be positive and finite, or infinite")
149            }
150            Self::InvalidPlaybackRate => {
151                formatter.write_str("animation playback rate must be finite and non-zero")
152            }
153            Self::InvalidKeyframes => {
154                formatter.write_str("keyframes must be finite, sorted, and cover offsets 0..=1")
155            }
156        }
157    }
158}
159
160impl std::error::Error for TimingError {}