native_windows_gui2/controls/
animation_timer.rs

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