native_windows_gui/controls/
animation_timer.rs

1use crate::controls::ControlHandle;
2use crate::NwgError;
3use crate::win32::window_helper as wh;
4use std::{thread, time::{Duration, Instant}, sync::{Mutex, Arc}};
5
6use winapi::um::winuser::SendNotifyMessageW;
7use winapi::shared::minwindef::{WPARAM, LPARAM};
8use winapi::shared::windef::HWND;
9
10const NOT_BOUND: &'static str = "AnimationTimer is not yet bound to a winapi object";
11const BAD_HANDLE: &'static str = "INTERNAL ERROR: AnimationTimer handle is not Timer!";
12
13lazy_static! {
14    
15    static ref THREAD_STATE: Arc<Mutex<AnimationThread>> = {
16        let state = AnimationThread {
17            timers: Vec::new(),
18        };
19
20        let state = Arc::new(Mutex::new(state));
21        let shared_state = state.clone();
22        
23        thread::spawn(move || {
24            let sleep_time = Duration::from_millis(1);
25
26            loop {
27                let mut state = shared_state.lock().unwrap();
28
29                for (id, timer) in state.timers.iter_mut().enumerate() {
30                    let timer = match timer.as_mut() {
31                        Some(t) => match t.active {
32                            true => t,
33                            false => { continue; }
34                        },
35                        None => { continue; }
36                    };
37
38                    if timer.last_tick.elapsed() > timer.interval {
39                        AnimationThread::timer_tick(id as u32, timer.hwnd);
40                        timer.last_tick = Instant::now();
41                        timer.current_tick += 1;
42
43                        if Some(timer.current_tick) == timer.max_tick {
44                            AnimationThread::timer_stop(id as u32, timer.hwnd);
45                            timer.active = false;
46
47                            // Must not trigger timer_stop twice due to birthtime
48                            continue;
49                        }
50                    }
51
52                    if let Some(lf) = timer.lifetime {
53                        if timer.birthtime.elapsed() > lf {
54                            AnimationThread::timer_stop(id as u32, timer.hwnd);
55                            timer.active = false;
56                        }
57                    }
58                }
59
60                drop(state);
61                thread::sleep(sleep_time);
62            }
63        });
64
65        state
66    };
67}
68
69#[derive(Copy, Clone)]
70struct InnerTimer {
71    interval: Duration,
72    last_tick: Instant,
73    lifetime: Option<Duration>,
74    birthtime: Instant,
75    max_tick: Option<u64>,
76    current_tick: u64,
77    active: bool,
78    hwnd: usize,
79}
80
81struct AnimationThread {
82    timers: Vec<Option<InnerTimer>>,
83}
84
85impl AnimationThread {
86
87    fn add_timer(inner: InnerTimer) -> u32 {
88        let mut state = THREAD_STATE.lock().unwrap();
89        
90        let empty = state.timers
91            .iter_mut()
92            .enumerate()
93            .find(|(_i, t)| t.is_none());
94
95        match empty {
96            Some((i, t)) => {
97                *t = Some(inner);
98                i as u32
99            },
100            None => {
101                state.timers.push(Some(inner));
102                (state.timers.len() - 1) as u32
103            }
104        }
105    }
106
107    fn reset_timer(id: u32) {
108        let mut state = THREAD_STATE.lock().unwrap();
109        if let Some(Some(t)) = state.timers.get_mut(id as usize) {
110            t.active = true;
111            t.birthtime = Instant::now();
112            t.current_tick = 0;
113        }
114    }
115
116    fn update_timer(id: u32, interval: Option<Duration>, lifetime: Option<Option<Duration>>, max_tick: Option<Option<u64>>) {
117        let mut state = THREAD_STATE.lock().unwrap();
118        if let Some(Some(t)) = state.timers.get_mut(id as usize) {
119            if let Some(v) = interval {
120                t.interval = v;
121            }
122
123            if let Some(v) = lifetime {
124                t.lifetime = v;
125            }
126
127            if let Some(v) = max_tick {
128                t.max_tick = v;
129            }
130        }
131    }
132
133    fn stop_timer(id: u32) {
134        let mut state = THREAD_STATE.lock().unwrap();
135        if let Some(Some(t)) = state.timers.get_mut(id as usize) {
136            t.active = false;
137        }
138    }
139
140    fn remove_timer(id: u32) {
141        let mut state = THREAD_STATE.lock().unwrap();
142        if let Some(t) = state.timers.get_mut(id as usize) {
143            *t = None;
144        }
145    }
146
147    pub fn timer_tick(id: u32, hwnd: usize) {
148        unsafe {
149            SendNotifyMessageW(hwnd as HWND, wh::NWG_TIMER_TICK, id as WPARAM, hwnd as LPARAM);
150        }
151    }
152
153    pub fn timer_stop(id: u32, hwnd: usize) {
154        unsafe {
155            SendNotifyMessageW(hwnd as HWND, wh::NWG_TIMER_STOP, id as WPARAM, hwnd as LPARAM);
156        }
157    }
158
159}
160
161
162/**
163A timer is an invisible UI component that trigger the `OnTimerTick` event at the specified interval.
164Timers are mosty used to handle animations OR to create a timeout. To sync multithreaded action see the `Notice` object.
165
166AnimationTimer is controlled from a singletion running in another thread. All instance of AnimationTimer will live on that thread.
167
168A timer still requires a top level window parent. If the top level window parent is destroyed, the timer becomes invalid.
169
170AnimationTimer replaces the default winapi timer. Please, for the love of god, do not use the default timer.
171
172**Builder parameters:**
173    * `parent`:     **Required.** The timer parent container that will receive the timer event. Should be a top level window
174    * `interval`:   The timer tick interval as a rust Duration. Minimum is 1 ms
175    * `lifetime`:   The timer should automatically stop after the selected Duration. Defaults to `None`.
176    * `max_tick`:   The timer should automatically stop after sending X amount of OnTImerTick events. Defaults to `None`.
177    * `active`:     If the timer should start right away. Default to `false`
178
179**Control events:**
180    * `OnTimerTick`: When the timer ticks
181    * `OnTimerStop`: When the timer stops itself (due to max_tick_count or lifetime being reached, not user actions)
182
183```
184use native_windows_gui as nwg;
185use std::time::Duration;
186
187/// Builds a timer that will animation something at 60fps for 3 sec
188fn build_timer(parent: &nwg::Window)  {
189    let mut timer = Default::default();
190    nwg::AnimationTimer::builder()
191        .parent(parent)
192        .interval(Duration::from_millis(1000/60))
193        .lifetime(Some(Duration::from_millis(3000)))
194        .build(&mut timer);
195}
196```
197*/
198#[derive(Default, PartialEq, Eq)]
199pub struct AnimationTimer {
200    pub handle: ControlHandle,
201}
202
203impl AnimationTimer {
204
205    pub fn builder() -> AnimationTimerBuilder {
206        AnimationTimerBuilder {
207            parent: None,
208            interval: Duration::from_millis(1000/60),
209            max_tick: None,
210            lifetime: None,
211            active: false,
212        }
213    }
214
215    /// Checks if the timer is still usable. A timer becomes unusable when the parent window is destroyed.
216    /// This will also return false if the timer is not initialized.
217    pub fn valid(&self) -> bool {
218        if self.handle.blank() { return false; }
219        let (hwnd, _) = self.handle.timer().expect(BAD_HANDLE);
220        wh::window_valid(hwnd)
221    }
222
223    /**
224        Start the selected timer. If the timer is already running this resets it.
225        This resets the life time and tick count if relevant.
226    */
227    pub fn start(&self) {
228        if self.handle.blank() { panic!("{}", NOT_BOUND); }
229        let (_, id) = self.handle.timer().expect(BAD_HANDLE);
230        AnimationThread::reset_timer(id);
231    }
232
233    /**
234        Stop the selected timer. If the timer is already stopped, this does nothing.
235    */
236    pub fn stop(&self) {
237        if self.handle.blank() { panic!("{}", NOT_BOUND); }
238        let (_, id) = self.handle.timer().expect(BAD_HANDLE);
239        AnimationThread::stop_timer(id);
240    }
241
242    /// Sets the interval on the this timer
243    pub fn set_interval(&self, i: Duration) {
244        if self.handle.blank() { panic!("{}", NOT_BOUND); }
245        let (_, id) = self.handle.timer().expect(BAD_HANDLE);
246        AnimationThread::update_timer(id, Some(i), None, None);
247    }
248
249    /// Sets the life time on the this timer
250    pub fn set_lifetime(&self, life: Option<Duration>) {
251        if self.handle.blank() { panic!("{}", NOT_BOUND); }
252        let (_, id) = self.handle.timer().expect(BAD_HANDLE);
253        AnimationThread::update_timer(id, None, Some(life), None);
254    }
255
256    /// Sets the max tick count on the this timer
257    pub fn set_max_tick(&self, max_tick: Option<u64>) {
258        if self.handle.blank() { panic!("{}", NOT_BOUND); }
259        let (_, id) = self.handle.timer().expect(BAD_HANDLE);
260        AnimationThread::update_timer(id, None, None, Some(max_tick));
261    }
262
263}
264
265impl Drop for AnimationTimer {
266
267    fn drop(&mut self) {
268        match &self.handle {
269            ControlHandle::Timer(_, id) => {
270                AnimationThread::remove_timer(*id);
271            },
272            _ => {}
273        }
274    }
275
276}
277
278pub struct AnimationTimerBuilder {
279    parent: Option<ControlHandle>,
280    interval: Duration,
281    max_tick: Option<u64>,
282    lifetime: Option<Duration>,
283    active: bool
284}
285
286impl AnimationTimerBuilder {
287
288    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> AnimationTimerBuilder {
289        self.parent = Some(p.into());
290        self
291    }
292
293    pub fn interval(mut self, interval: Duration) -> AnimationTimerBuilder {
294        self.interval = interval;
295        self
296    }
297
298    pub fn max_tick(mut self, max_tick: Option<u64>) -> AnimationTimerBuilder {
299        self.max_tick = max_tick;
300        self
301    }
302
303    pub fn lifetime(mut self, lifetime: Option<Duration>) -> AnimationTimerBuilder {
304        self.lifetime = lifetime;
305        self
306    }
307
308    pub fn active(mut self, active: bool) -> AnimationTimerBuilder {
309        self.active = active;
310        self
311    }
312
313    pub fn build(self, out: &mut AnimationTimer) -> Result<(), NwgError> {
314        let parent = match self.parent {
315            Some(p) => match p.hwnd() {
316                Some(handle) => Ok(handle),
317                None => Err(NwgError::control_create("Wrong parent type"))
318            },
319            None => Err(NwgError::no_parent("Timer"))
320        }?;
321
322        if self.interval < Duration::from_millis(1) {
323            return Err(NwgError::control_create("Timer interval cannot be smaller than 1 ms"));
324        }
325
326        let inner = InnerTimer {
327            interval: self.interval,
328            last_tick: Instant::now(),
329            lifetime: self.lifetime,
330            birthtime: Instant::now(),
331            max_tick: self.max_tick,
332            current_tick: 0,
333            active: self.active,
334            hwnd: parent as usize,
335        };
336
337        let id = AnimationThread::add_timer(inner);
338
339        *out = AnimationTimer {
340            handle: ControlHandle::Timer(parent, id)
341        };
342
343        Ok(())
344    }
345
346}