dioxus_timer/
lib.rs

1use dioxus::prelude::*;
2use std::fmt::Display;
3
4#[cfg(target_arch = "wasm32")]
5use async_std::task::sleep;
6#[cfg(target_arch = "wasm32")]
7use instant::{Duration, Instant};
8#[cfg(not(target_arch = "wasm32"))]
9use std::time::{Duration, Instant};
10#[cfg(not(target_arch = "wasm32"))]
11use tokio::time::sleep;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum TimerState {
15    Inactive,
16    Working,
17    Finished,
18    Paused,
19}
20
21impl Display for TimerState {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        let text = match self {
24            TimerState::Inactive => "Inactive",
25            TimerState::Working => "Working",
26            TimerState::Finished => "Finished",
27            TimerState::Paused => "Paused",
28        };
29        write!(f, "{text}")
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct DioxusTimer {
35    preset_duration: Duration,
36    target_time: Instant,
37    state: TimerState,
38    /// Stores Instant::now()
39    current_time: Instant,
40    /// Stores paused time
41    paused_time: Option<Instant>,
42}
43
44impl DioxusTimer {
45    /// Creates a new `DioxusTimer` instance with default settings.
46    pub fn new() -> Self {
47        let current_time = Instant::now();
48        let target_time = current_time;
49        Self {
50            preset_duration: Duration::ZERO,
51            target_time,
52            state: TimerState::Inactive,
53            current_time,
54            paused_time: None,
55        }
56    }
57
58    /// Sets the preset duration for the timer.
59    pub fn set_preset_time(&mut self, preset_duration: Duration) {
60        if self.state == TimerState::Finished {
61            return;
62        }
63        self.preset_duration = preset_duration;
64        self.target_time = self
65            .current_time
66            .checked_add(preset_duration)
67            .unwrap_or(self.current_time);
68    }
69
70    /// Returns the remaining time on the timer.
71    pub fn remaining_time(&self) -> Duration {
72        self.target_time
73            .checked_duration_since(self.current_time)
74            .unwrap_or(Duration::ZERO)
75    }
76
77    /// Returns the current state of the timer.
78    pub fn state(&self) -> TimerState {
79        self.state
80    }
81
82    /// Starts the timer if it is in the `Inactive` state.
83    ///
84    /// If the preset duration is zero, the method does nothing.
85    pub fn start(&mut self) {
86        match self.state {
87            TimerState::Inactive => {
88                if self.preset_duration.is_zero() {
89                    return;
90                }
91                self.target_time = self
92                    .current_time
93                    .checked_add(self.preset_duration)
94                    .unwrap_or(self.current_time);
95                self.state = TimerState::Working;
96            }
97            TimerState::Paused => {
98                self.state = TimerState::Working;
99            }
100            _ => {}
101        }
102    }
103
104    /// Pauses the timer if it is in the `Working` state.
105    pub fn pause(&mut self) {
106        if let TimerState::Working = self.state {
107            self.state = TimerState::Paused;
108            self.paused_time = Some(Instant::now());
109        }
110    }
111
112    /// Resets the timer to its initial state or sets the target time for a new cycle.
113    ///
114    /// If the timer is in the `Finished` state, it transitions to the `Inactive` state.
115    pub fn reset(&mut self) {
116        if self.state == TimerState::Finished {
117            self.state = TimerState::Inactive;
118            return;
119        }
120        self.target_time = self
121            .current_time
122            .checked_add(self.preset_duration)
123            .unwrap_or(self.current_time);
124    }
125
126    /// Updates the timer's current time and checks for state transitions.
127    ///
128    /// The `Working` state transitions to `Finished` when the target time is reached.
129    /// The `Paused` state adjusts the target time based on the time paused.
130    /// The `Inactive` state resets the timer.
131    pub fn update(&mut self) {
132        self.current_time = Instant::now();
133        match self.state {
134            TimerState::Working => {
135                if self
136                    .target_time
137                    .checked_duration_since(self.current_time)
138                    .is_none()
139                {
140                    self.state = TimerState::Finished;
141                }
142            }
143            TimerState::Paused => {
144                self.target_time = self
145                    .target_time
146                    .checked_add(self.current_time - self.paused_time.unwrap())
147                    .unwrap_or(self.current_time);
148                self.paused_time = Some(self.current_time);
149            }
150            TimerState::Inactive => {
151                self.reset();
152            }
153            _ => {}
154        }
155    }
156}
157
158impl Default for DioxusTimer {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164impl Display for DioxusTimer {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        let rem_time = self.remaining_time().as_secs();
167        write!(
168            f,
169            "{:0>2}:{:0>2}:{:0>2}",
170            rem_time / 3600,
171            rem_time % 3600 / 60,
172            rem_time % 60,
173        )
174    }
175}
176
177/// Manages a DioxusTimer instance within the Dioxus GUI framework.
178///
179/// # Examples
180///
181/// ```
182///let mut timer = use_timer(Duration::from_millis(16));
183///use_effect(move || {
184///    spawn(async move {
185///        timer.write().set_preset_time(Duration::from_secs(10));
186///        timer.write().start();
187///    });
188///});
189///rsx!("{timer}"
190/// ```
191pub fn use_timer(tick: Duration) -> Signal<DioxusTimer> {
192    let mut timer = use_signal(DioxusTimer::new);
193    use_future(move || async move {
194        loop {
195            timer.write().update();
196            sleep(tick).await;
197        }
198    });
199    timer
200}