1use std::time::Duration;
6
7use blockworx_geom::{Bounded, Bounds};
8pub struct ProgressBounds;
9
10impl Bounds for ProgressBounds {
11 const MIN: f32 = 0.0;
12 const MAX: f32 = 1.0;
13}
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
19pub struct Progress(Bounded<ProgressBounds>);
20
21impl Progress {
22 pub const fn new(value: f32) -> Self {
23 Self(Bounded::new(value))
24 }
25
26 pub const fn get(self) -> f32 {
27 self.0.get()
28 }
29
30 pub const fn zero() -> Self {
31 Self(Bounded::min_value())
32 }
33
34 pub const fn one() -> Self {
35 Self(Bounded::max_value())
36 }
37
38 pub fn is_complete(self) -> bool {
39 self == Self::one()
40 }
41
42 pub fn through(elapsed: Duration, total: Duration) -> Self {
44 if total.is_zero() {
45 return Self::one();
46 }
47 Self::new(elapsed.div_duration_f32(total))
48 }
49
50 pub fn complement(self) -> Self {
53 Self::new(1.0 - self.get())
54 }
55
56 pub fn eased(self) -> Self {
59 let t = self.get();
60 Self::new(t * t * (3.0 - 2.0 * t))
61 }
62}
63
64impl From<Progress> for f32 {
65 fn from(progress: Progress) -> f32 {
66 progress.get()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn through_is_the_clamped_ratio() {
76 let total = Duration::from_secs(2);
77 assert_eq!(Progress::through(Duration::from_secs(1), total).get(), 0.5);
78 assert_eq!(
79 Progress::through(Duration::from_secs(5), total),
80 Progress::one()
81 );
82 assert_eq!(Progress::through(Duration::ZERO, total), Progress::zero());
83 assert_eq!(
84 Progress::through(Duration::from_secs(1), Duration::ZERO),
85 Progress::one()
86 );
87 }
88
89 #[test]
90 fn complement_mirrors_around_the_midpoint() {
91 assert_eq!(Progress::new(0.25).complement().get(), 0.75);
92 assert_eq!(Progress::one().complement(), Progress::zero());
93 }
94}