1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! `rusty_time` is a simple timer library.
//!
//! Use it in a loop like this:
//!
//! ```rust
//! use std::time::{Duration, Instant};
//!
//! use rusty_time::Timer;
//!
//! fn main() {
//!     let mut timer = Timer::new(Duration::from_secs_f32(1.5));
//!
//!     let mut start_time = Instant::now();
//!     loop {
//!         timer.tick(start_time.elapsed());
//!         start_time = Instant::now();
//!         println!(
//!             "Time on timer: {:.2}s ({:.1}%)",
//!             timer.remaining().as_secs_f32(),
//!             timer.percent_left() * 100.0
//!         );
//!         if timer.just_finished() {
//!             break;
//!         }
//!     }
//!     println!("Timer finished!");
//! }
//! ```

use std::time::Duration;

/// A simple timer that is externally driven. [`.tick()`](Timer::tick) must be called with a delta
/// time for the timer to advance.
///
/// Use it in a loop like this:
///
/// ```rust
/// use std::time::{Duration, Instant};
///
/// use rusty_time::Timer;
///
/// fn main() {
///     let mut timer = Timer::new(Duration::from_secs_f32(1.5));
///
///     let mut start_time = Instant::now();
///     loop {
///         timer.tick(start_time.elapsed());
///         start_time = Instant::now();
///         println!(
///             "Time on timer: {:.2}s ({:.1}%)",
///             timer.remaining().as_secs_f32(),
///             timer.percent_left() * 100.0
///         );
///         if timer.just_finished() {
///             break;
///         }
///     }
///     println!("Timer finished!");
/// }
/// ```
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Timer {
    duration: Duration,
    remaining: Duration,
    finished: bool,
    just_finished: bool,
}

impl Timer {
    /// Create a new timer with the given duration
    pub fn new(duration: Duration) -> Self {
        Self {
            duration,
            remaining: duration,
            finished: false,
            just_finished: false,
        }
    }

    /// Advance the timer by a specific duration. This _must_ be called for the timer to be useful.
    pub fn tick(&mut self, delta: Duration) -> &mut Self {
        self.just_finished = false;
        if self.finished {
            return self;
        }
        self.remaining = self.remaining.saturating_sub(delta);
        if self.remaining == Duration::ZERO {
            self.just_finished = true;
            self.finished = true;
        }
        self
    }

    /// Whether the timer hit zero since the last call to [`tick`](Timer::tick).
    pub fn just_finished(&self) -> bool {
        self.just_finished
    }

    /// Whether the timer is at zero.
    pub fn finished(&self) -> bool {
        self.finished
    }

    /// Reset the timer to the starting duration. Resets `finished` and `just_finished` as well.
    pub fn reset(&mut self) {
        self.set_remaining(self.duration);
    }

    /// Returns percent of timer elapsed (from 0.0 to 1.0)
    pub fn percent(&self) -> f32 {
        if self.duration == Duration::ZERO {
            1.0
        } else {
            (self.duration - self.remaining).as_secs_f32() / self.duration.as_secs_f32()
        }
    }

    /// Returns percent of timer remaining (from 1.0 to 0.0)
    pub fn percent_left(&self) -> f32 {
        1.0 - self.percent()
    }

    /// Returns the full duration of the timer when newly started. See also
    /// [`remaining`](Timer::remaining) and [`elapsed`](Timer::elapsed)
    pub fn duration(&self) -> Duration {
        self.duration
    }

    /// Sets the starting duration of the timer, but does not change the remaining time on the timer.
    /// See [`reset`](Timer::reset) to reset the remaining time to the starting duration.
    pub fn set_duration(&mut self, duration: Duration) {
        self.duration = duration;
    }

    /// Returns the time currently remaining on the timer.
    pub fn remaining(&self) -> Duration {
        self.remaining
    }

    /// Sets the time currently remaining on the timer. Sets/unsets that the timer has finished as
    /// appropriate.
    pub fn set_remaining(&mut self, remaining: Duration) {
        if remaining == Duration::ZERO && self.remaining != Duration::ZERO {
            self.just_finished = true;
            self.finished = true;
        } else {
            self.just_finished = false;
            self.finished = false;
        }
        self.remaining = remaining.clamp(Duration::ZERO, self.duration);
    }

    /// Returns the time that has elapsed on the timer.
    pub fn elapsed(&self) -> Duration {
        self.duration - self.remaining
    }

    /// Sets the time currently elapsed on the timer. Sets/unsets that the timer has finished as
    /// appropriate.
    pub fn set_elapsed(&mut self, elapsed: Duration) {
        self.set_remaining(self.duration.saturating_sub(elapsed));
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn timer_works() {
        let mut timer = Timer::new(Duration::from_millis(500));
        assert_eq!(timer.percent(), 0.0);
        assert_eq!(timer.percent_left(), 1.0);
        timer.tick(Duration::from_millis(300));
        assert_eq!(timer.finished(), false);
        assert_eq!(timer.just_finished(), false);
        timer.tick(Duration::from_millis(300));
        assert_eq!(timer.finished(), true);
        assert_eq!(timer.just_finished(), true);
        assert_eq!(timer.remaining(), Duration::ZERO);
        timer.tick(Duration::from_millis(300));
        assert_eq!(timer.finished(), true);
        assert_eq!(timer.just_finished(), false);
        assert_eq!(timer.remaining(), Duration::ZERO);
        timer.reset();
        assert_eq!(timer.finished, false);
        assert_eq!(timer.remaining, Duration::from_millis(500));
        timer.set_elapsed(Duration::from_millis(250));
        assert_eq!(timer.percent(), 0.5);
        assert_eq!(timer.percent_left(), 0.5);
        assert_eq!(timer.remaining(), Duration::from_millis(250));
    }
}