1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial |
3 | |
4 | // cSpell: ignore singleshot |
5 | |
6 | /*! |
7 | Support for timers. |
8 | |
9 | Timers are just a bunch of callbacks sorted by expiry date. |
10 | */ |
11 | |
12 | #![warn (missing_docs)] |
13 | #[cfg (not(feature = "std" ))] |
14 | use alloc::boxed::Box; |
15 | #[cfg (not(feature = "std" ))] |
16 | use alloc::vec::Vec; |
17 | use core::{ |
18 | cell::{Cell, RefCell}, |
19 | num::NonZeroUsize, |
20 | }; |
21 | |
22 | use crate::animations::Instant; |
23 | |
24 | type TimerCallback = Box<dyn FnMut()>; |
25 | type SingleShotTimerCallback = Box<dyn FnOnce()>; |
26 | |
27 | /// The TimerMode specifies what should happen after the timer fired. |
28 | /// |
29 | /// Used by the [`Timer::start()`] function. |
30 | #[derive (Copy, Clone)] |
31 | #[repr (u8)] |
32 | #[non_exhaustive ] |
33 | pub enum TimerMode { |
34 | /// A SingleShot timer is fired only once. |
35 | SingleShot, |
36 | /// A Repeated timer is fired repeatedly until it is stopped or dropped. |
37 | Repeated, |
38 | } |
39 | |
40 | /// Timer is a handle to the timer system that allows triggering a callback to be called |
41 | /// after a specified period of time. |
42 | /// |
43 | /// Use [`Timer::start()`] to create a timer that can repeat at frequent interval, or |
44 | /// [`Timer::single_shot`] if you just want to call a function with a delay and do not |
45 | /// need to be able to stop it. |
46 | /// |
47 | /// The timer will automatically stop when dropped. You must keep the Timer object |
48 | /// around for as long as you want the timer to keep firing. |
49 | /// |
50 | /// The timer can only be used in the thread that runs the Slint event loop. |
51 | /// They will not fire if used in another thread. |
52 | /// |
53 | /// ## Example |
54 | /// ```rust,no_run |
55 | /// # i_slint_backend_testing::init(); |
56 | /// use slint::{Timer, TimerMode}; |
57 | /// let timer = Timer::default(); |
58 | /// timer.start(TimerMode::Repeated, std::time::Duration::from_millis(200), move || { |
59 | /// println!("This will be printed every 200ms." ); |
60 | /// }); |
61 | /// // ... more initialization ... |
62 | /// slint::run_event_loop(); |
63 | /// ``` |
64 | #[derive (Default)] |
65 | pub struct Timer { |
66 | id: Cell<Option<NonZeroUsize>>, |
67 | } |
68 | |
69 | impl Timer { |
70 | /// Starts the timer with the given mode and interval, in order for the callback to called when the |
71 | /// timer fires. If the timer has been started previously and not fired yet, then it will be restarted. |
72 | /// |
73 | /// Arguments: |
74 | /// * `mode`: The timer mode to apply, i.e. whether to repeatedly fire the timer or just once. |
75 | /// * `interval`: The duration from now until when the timer should fire. And the period of that timer |
76 | /// for [`Repeated`](TimerMode::Repeated) timers. |
77 | /// * `callback`: The function to call when the time has been reached or exceeded. |
78 | pub fn start( |
79 | &self, |
80 | mode: TimerMode, |
81 | interval: core::time::Duration, |
82 | callback: impl FnMut() + 'static, |
83 | ) { |
84 | CURRENT_TIMERS.with(|timers| { |
85 | let mut timers = timers.borrow_mut(); |
86 | let id = timers.start_or_restart_timer( |
87 | self.id(), |
88 | mode, |
89 | interval, |
90 | CallbackVariant::MultiFire(Box::new(callback)), |
91 | ); |
92 | self.set_id(Some(id)); |
93 | }) |
94 | } |
95 | |
96 | /// Starts the timer with the duration, in order for the callback to called when the |
97 | /// timer fires. It is fired only once and then deleted. |
98 | /// |
99 | /// Arguments: |
100 | /// * `duration`: The duration from now until when the timer should fire. |
101 | /// * `callback`: The function to call when the time has been reached or exceeded. |
102 | /// |
103 | /// ## Example |
104 | /// ```rust |
105 | /// # i_slint_backend_testing::init(); |
106 | /// use slint::Timer; |
107 | /// Timer::single_shot(std::time::Duration::from_millis(200), move || { |
108 | /// println!("This will be printed after 200ms." ); |
109 | /// }); |
110 | /// ``` |
111 | pub fn single_shot(duration: core::time::Duration, callback: impl FnOnce() + 'static) { |
112 | CURRENT_TIMERS.with(|timers| { |
113 | let mut timers = timers.borrow_mut(); |
114 | timers.start_or_restart_timer( |
115 | None, |
116 | TimerMode::SingleShot, |
117 | duration, |
118 | CallbackVariant::SingleShot(Box::new(callback)), |
119 | ); |
120 | }) |
121 | } |
122 | |
123 | /// Stops the previously started timer. Does nothing if the timer has never been started. |
124 | pub fn stop(&self) { |
125 | if let Some(id) = self.id() { |
126 | CURRENT_TIMERS.with(|timers| { |
127 | timers.borrow_mut().deactivate_timer(id); |
128 | }); |
129 | } |
130 | } |
131 | |
132 | /// Restarts the timer. If the timer was previously started by calling [`Self::start()`] |
133 | /// with a duration and callback, then the time when the callback will be next invoked |
134 | /// is re-calculated to be in the specified duration relative to when this function is called. |
135 | /// |
136 | /// Does nothing if the timer was never started. |
137 | pub fn restart(&self) { |
138 | if let Some(id) = self.id() { |
139 | CURRENT_TIMERS.with(|timers| { |
140 | timers.borrow_mut().deactivate_timer(id); |
141 | timers.borrow_mut().activate_timer(id); |
142 | }); |
143 | } |
144 | } |
145 | |
146 | /// Returns true if the timer is running; false otherwise. |
147 | pub fn running(&self) -> bool { |
148 | self.id() |
149 | .map(|timer_id| CURRENT_TIMERS.with(|timers| timers.borrow().timers[timer_id].running)) |
150 | .unwrap_or(false) |
151 | } |
152 | |
153 | /// Change the duration of timer. If the timer was previously started by calling [`Self::start()`] |
154 | /// with a duration and callback, then the time when the callback will be next invoked |
155 | /// is re-calculated to be in the specified duration relative to when this function is called. |
156 | /// |
157 | /// Does nothing if the timer was never started. |
158 | /// |
159 | /// Arguments: |
160 | /// * `interval`: The duration from now until when the timer should fire. And the period of that timer |
161 | /// for [`Repeated`](TimerMode::Repeated) timers. |
162 | pub fn set_interval(&self, interval: core::time::Duration) { |
163 | if let Some(id) = self.id() { |
164 | CURRENT_TIMERS.with(|timers| { |
165 | timers.borrow_mut().set_interval(id, interval); |
166 | }); |
167 | } |
168 | } |
169 | |
170 | fn id(&self) -> Option<usize> { |
171 | self.id.get().map(|v| usize::from(v) - 1) |
172 | } |
173 | |
174 | fn set_id(&self, id: Option<usize>) { |
175 | self.id.set(id.and_then(|v| NonZeroUsize::new(v + 1))); |
176 | } |
177 | } |
178 | |
179 | impl Drop for Timer { |
180 | fn drop(&mut self) { |
181 | if let Some(id: usize) = self.id() { |
182 | let _ = CURRENT_TIMERS.try_with(|timers: &RefCell| { |
183 | timers.borrow_mut().remove_timer(id); |
184 | }); |
185 | } |
186 | } |
187 | } |
188 | |
189 | enum CallbackVariant { |
190 | Empty, |
191 | MultiFire(TimerCallback), |
192 | SingleShot(SingleShotTimerCallback), |
193 | } |
194 | |
195 | struct TimerData { |
196 | duration: core::time::Duration, |
197 | mode: TimerMode, |
198 | running: bool, |
199 | /// Set to true when it is removed when the callback is still running |
200 | removed: bool, |
201 | /// true if it is in the cached the active_timers list in the maybe_activate_timers stack |
202 | being_activated: bool, |
203 | |
204 | callback: CallbackVariant, |
205 | } |
206 | |
207 | #[derive (Clone, Copy)] |
208 | struct ActiveTimer { |
209 | id: usize, |
210 | timeout: Instant, |
211 | } |
212 | |
213 | /// TimerList provides the interface to the event loop for activating times and |
214 | /// determining the nearest timeout. |
215 | #[derive (Default)] |
216 | pub struct TimerList { |
217 | timers: slab::Slab<TimerData>, |
218 | active_timers: Vec<ActiveTimer>, |
219 | /// If a callback is currently running, this is the id of the currently running callback |
220 | callback_active: Option<usize>, |
221 | } |
222 | |
223 | impl TimerList { |
224 | /// Returns the timeout of the timer that should fire the soonest, or None if there |
225 | /// is no timer active. |
226 | pub fn next_timeout() -> Option<Instant> { |
227 | CURRENT_TIMERS.with(|timers| { |
228 | timers |
229 | .borrow() |
230 | .active_timers |
231 | .first() |
232 | .map(|first_active_timer| first_active_timer.timeout) |
233 | }) |
234 | } |
235 | |
236 | /// Activates any expired timers by calling their callback function. Returns true if any timers were |
237 | /// activated; false otherwise. |
238 | pub fn maybe_activate_timers(now: Instant) -> bool { |
239 | // Shortcut: Is there any timer worth activating? |
240 | if TimerList::next_timeout().map(|timeout| now < timeout).unwrap_or(false) { |
241 | return false; |
242 | } |
243 | |
244 | CURRENT_TIMERS.with(|timers| { |
245 | assert!(timers.borrow().callback_active.is_none(), "Recursion in timer code" ); |
246 | |
247 | let mut any_activated = false; |
248 | |
249 | // The active timer list is cleared here and not-yet-fired ones are inserted below, in order to allow |
250 | // timer callbacks to register their own timers. |
251 | let timers_to_process = core::mem::take(&mut timers.borrow_mut().active_timers); |
252 | { |
253 | let mut timers = timers.borrow_mut(); |
254 | for active_timer in &timers_to_process { |
255 | let timer = &mut timers.timers[active_timer.id]; |
256 | assert!(!timer.being_activated); |
257 | timer.being_activated = true; |
258 | } |
259 | } |
260 | for active_timer in timers_to_process.into_iter() { |
261 | if active_timer.timeout <= now { |
262 | any_activated = true; |
263 | |
264 | let mut callback = { |
265 | let mut timers = timers.borrow_mut(); |
266 | |
267 | timers.callback_active = Some(active_timer.id); |
268 | |
269 | // do it before invoking the callback, in case the callback wants to stop or adjust its own timer |
270 | if matches!(timers.timers[active_timer.id].mode, TimerMode::Repeated) { |
271 | timers.activate_timer(active_timer.id); |
272 | } else { |
273 | timers.timers[active_timer.id].running = false; |
274 | } |
275 | |
276 | // have to release the borrow on `timers` before invoking the callback, |
277 | // so here we temporarily move the callback out of its permanent place |
278 | core::mem::replace( |
279 | &mut timers.timers[active_timer.id].callback, |
280 | CallbackVariant::Empty, |
281 | ) |
282 | }; |
283 | |
284 | match callback { |
285 | CallbackVariant::Empty => (), |
286 | CallbackVariant::MultiFire(ref mut cb) => cb(), |
287 | CallbackVariant::SingleShot(cb) => { |
288 | cb(); |
289 | timers.borrow_mut().callback_active = None; |
290 | timers.borrow_mut().timers.remove(active_timer.id); |
291 | continue; |
292 | } |
293 | }; |
294 | |
295 | let mut timers = timers.borrow_mut(); |
296 | |
297 | let callback_register = &mut timers.timers[active_timer.id].callback; |
298 | |
299 | // only emplace back the callback if its permanent store is still Empty: |
300 | // if not, it means the invoked callback has restarted its own timer with a new callback |
301 | if matches!(callback_register, CallbackVariant::Empty) { |
302 | *callback_register = callback; |
303 | } |
304 | |
305 | timers.callback_active = None; |
306 | let t = &mut timers.timers[active_timer.id]; |
307 | if t.removed { |
308 | timers.timers.remove(active_timer.id); |
309 | } else { |
310 | t.being_activated = false; |
311 | } |
312 | } else { |
313 | let mut timers = timers.borrow_mut(); |
314 | let t = &mut timers.timers[active_timer.id]; |
315 | if t.removed { |
316 | timers.timers.remove(active_timer.id); |
317 | } else { |
318 | t.being_activated = false; |
319 | timers.register_active_timer(active_timer); |
320 | } |
321 | } |
322 | } |
323 | any_activated |
324 | }) |
325 | } |
326 | |
327 | fn start_or_restart_timer( |
328 | &mut self, |
329 | id: Option<usize>, |
330 | mode: TimerMode, |
331 | duration: core::time::Duration, |
332 | callback: CallbackVariant, |
333 | ) -> usize { |
334 | let timer_data = TimerData { |
335 | duration, |
336 | mode, |
337 | running: false, |
338 | removed: false, |
339 | callback, |
340 | being_activated: false, |
341 | }; |
342 | let inactive_timer_id = if let Some(id) = id { |
343 | self.deactivate_timer(id); |
344 | self.timers[id] = timer_data; |
345 | id |
346 | } else { |
347 | self.timers.insert(timer_data) |
348 | }; |
349 | self.activate_timer(inactive_timer_id); |
350 | inactive_timer_id |
351 | } |
352 | |
353 | fn deactivate_timer(&mut self, id: usize) { |
354 | let mut i = 0; |
355 | while i < self.active_timers.len() { |
356 | if self.active_timers[i].id == id { |
357 | self.active_timers.remove(i); |
358 | self.timers[id].running = false; |
359 | break; |
360 | } else { |
361 | i += 1; |
362 | } |
363 | } |
364 | } |
365 | |
366 | fn activate_timer(&mut self, id: usize) { |
367 | self.register_active_timer(ActiveTimer { |
368 | id, |
369 | timeout: Instant::now() + self.timers[id].duration, |
370 | }); |
371 | } |
372 | |
373 | fn register_active_timer(&mut self, new_active_timer: ActiveTimer) { |
374 | let insertion_index = lower_bound(&self.active_timers, |existing_timer| { |
375 | existing_timer.timeout < new_active_timer.timeout |
376 | }); |
377 | |
378 | self.active_timers.insert(insertion_index, new_active_timer); |
379 | self.timers[new_active_timer.id].running = true; |
380 | } |
381 | |
382 | fn remove_timer(&mut self, id: usize) { |
383 | self.deactivate_timer(id); |
384 | let t = &mut self.timers[id]; |
385 | if t.being_activated { |
386 | t.removed = true; |
387 | } else { |
388 | self.timers.remove(id); |
389 | } |
390 | } |
391 | |
392 | fn set_interval(&mut self, id: usize, duration: core::time::Duration) { |
393 | let timer = &self.timers[id]; |
394 | |
395 | if !matches!(timer.callback, CallbackVariant::MultiFire { .. }) { |
396 | return; |
397 | } |
398 | |
399 | if timer.running { |
400 | self.deactivate_timer(id); |
401 | self.timers[id].duration = duration; |
402 | self.activate_timer(id); |
403 | } else { |
404 | self.timers[id].duration = duration; |
405 | } |
406 | } |
407 | } |
408 | |
409 | #[cfg (all(not(feature = "std" ), feature = "unsafe-single-threaded" ))] |
410 | use crate::unsafe_single_threaded::thread_local; |
411 | |
412 | thread_local!(static CURRENT_TIMERS : RefCell<TimerList> = RefCell::default()); |
413 | |
414 | fn lower_bound<T>(vec: &[T], mut less_than: impl FnMut(&T) -> bool) -> usize { |
415 | let mut left: usize = 0; |
416 | let mut right: usize = vec.len(); |
417 | |
418 | while left != right { |
419 | let mid: usize = left + (right - left) / 2; |
420 | let value: &T = &vec[mid]; |
421 | if less_than(value) { |
422 | left = mid + 1; |
423 | } else { |
424 | right = mid; |
425 | } |
426 | } |
427 | |
428 | left |
429 | } |
430 | |
431 | #[cfg (feature = "ffi" )] |
432 | pub(crate) mod ffi { |
433 | #![allow (unsafe_code)] |
434 | |
435 | use super::*; |
436 | #[allow (non_camel_case_types)] |
437 | type c_void = (); |
438 | |
439 | struct WrapFn { |
440 | callback: extern "C" fn(*mut c_void), |
441 | user_data: *mut c_void, |
442 | drop_user_data: Option<extern "C" fn(*mut c_void)>, |
443 | } |
444 | |
445 | impl Drop for WrapFn { |
446 | fn drop(&mut self) { |
447 | if let Some(x) = self.drop_user_data { |
448 | x(self.user_data) |
449 | } |
450 | } |
451 | } |
452 | |
453 | impl WrapFn { |
454 | fn call(&self) { |
455 | (self.callback)(self.user_data) |
456 | } |
457 | } |
458 | |
459 | /// Start a timer with the given mode, duration in millisecond and callback. A timer id may be provided (first argument). |
460 | /// A value of -1 for the timer id means a new timer is to be allocated. |
461 | /// The (new) timer id is returned. |
462 | /// The timer MUST be destroyed with slint_timer_destroy. |
463 | #[no_mangle ] |
464 | pub extern "C" fn slint_timer_start( |
465 | id: usize, |
466 | mode: TimerMode, |
467 | duration: u64, |
468 | callback: extern "C" fn(*mut c_void), |
469 | user_data: *mut c_void, |
470 | drop_user_data: Option<extern "C" fn(*mut c_void)>, |
471 | ) -> usize { |
472 | let wrap = WrapFn { callback, user_data, drop_user_data }; |
473 | let timer = Timer::default(); |
474 | if id != 0 { |
475 | timer.id.set(NonZeroUsize::new(id)); |
476 | } |
477 | timer.start(mode, core::time::Duration::from_millis(duration), move || wrap.call()); |
478 | timer.id.take().map(|x| usize::from(x)).unwrap_or(0) |
479 | } |
480 | |
481 | /// Execute a callback with a delay in millisecond |
482 | #[no_mangle ] |
483 | pub extern "C" fn slint_timer_singleshot( |
484 | delay: u64, |
485 | callback: extern "C" fn(*mut c_void), |
486 | user_data: *mut c_void, |
487 | drop_user_data: Option<extern "C" fn(*mut c_void)>, |
488 | ) { |
489 | let wrap = WrapFn { callback, user_data, drop_user_data }; |
490 | Timer::single_shot(core::time::Duration::from_millis(delay), move || wrap.call()); |
491 | } |
492 | |
493 | /// Stop a timer and free its raw data |
494 | #[no_mangle ] |
495 | pub extern "C" fn slint_timer_destroy(id: usize) { |
496 | if id == 0 { |
497 | return; |
498 | } |
499 | let timer = Timer { id: Cell::new(NonZeroUsize::new(id)) }; |
500 | drop(timer); |
501 | } |
502 | |
503 | /// Stop a timer |
504 | #[no_mangle ] |
505 | pub extern "C" fn slint_timer_stop(id: usize) { |
506 | if id == 0 { |
507 | return; |
508 | } |
509 | let timer = Timer { id: Cell::new(NonZeroUsize::new(id)) }; |
510 | timer.stop(); |
511 | timer.id.take(); // Make sure that dropping the Timer doesn't unregister it. C++ will call destroy() in the destructor. |
512 | } |
513 | |
514 | /// Restart a repeated timer |
515 | #[no_mangle ] |
516 | pub extern "C" fn slint_timer_restart(id: usize) { |
517 | if id == 0 { |
518 | return; |
519 | } |
520 | let timer = Timer { id: Cell::new(NonZeroUsize::new(id)) }; |
521 | timer.restart(); |
522 | timer.id.take(); // Make sure that dropping the Timer doesn't unregister it. C++ will call destroy() in the destructor. |
523 | } |
524 | |
525 | /// Returns true if the timer is running; false otherwise. |
526 | #[no_mangle ] |
527 | pub extern "C" fn slint_timer_running(id: usize) -> bool { |
528 | if id == 0 { |
529 | return false; |
530 | } |
531 | let timer = Timer { id: Cell::new(NonZeroUsize::new(id)) }; |
532 | let running = timer.running(); |
533 | timer.id.take(); // Make sure that dropping the Timer doesn't unregister it. C++ will call destroy() in the destructor. |
534 | running |
535 | } |
536 | } |
537 | |
538 | /** |
539 | ```rust |
540 | i_slint_backend_testing::init(); |
541 | use slint::{Timer, TimerMode}; |
542 | use std::{rc::Rc, cell::RefCell, time::Duration}; |
543 | #[derive(Default)] |
544 | struct SharedState { |
545 | timer_200: Timer, |
546 | timer_200_called: usize, |
547 | timer_500: Timer, |
548 | timer_500_called: usize, |
549 | timer_once: Timer, |
550 | timer_once_called: usize, |
551 | } |
552 | let state = Rc::new(RefCell::new(SharedState::default())); |
553 | // Note: state will be leaked because of circular dependencies: don't do that in production |
554 | let state_ = state.clone(); |
555 | state.borrow_mut().timer_200.start(TimerMode::Repeated, Duration::from_millis(200), move || { |
556 | state_.borrow_mut().timer_200_called += 1; |
557 | }); |
558 | let state_ = state.clone(); |
559 | state.borrow_mut().timer_once.start(TimerMode::Repeated, Duration::from_millis(300), move || { |
560 | state_.borrow_mut().timer_once_called += 1; |
561 | state_.borrow().timer_once.stop(); |
562 | }); |
563 | let state_ = state.clone(); |
564 | state.borrow_mut().timer_500.start(TimerMode::Repeated, Duration::from_millis(500), move || { |
565 | state_.borrow_mut().timer_500_called += 1; |
566 | }); |
567 | slint::platform::update_timers_and_animations(); |
568 | i_slint_core::tests::slint_mock_elapsed_time(100); |
569 | assert_eq!(state.borrow().timer_200_called, 0); |
570 | assert_eq!(state.borrow().timer_once_called, 0); |
571 | assert_eq!(state.borrow().timer_500_called, 0); |
572 | i_slint_core::tests::slint_mock_elapsed_time(100); |
573 | assert_eq!(state.borrow().timer_200_called, 1); |
574 | assert_eq!(state.borrow().timer_once_called, 0); |
575 | assert_eq!(state.borrow().timer_500_called, 0); |
576 | i_slint_core::tests::slint_mock_elapsed_time(100); |
577 | assert_eq!(state.borrow().timer_200_called, 1); |
578 | assert_eq!(state.borrow().timer_once_called, 1); |
579 | assert_eq!(state.borrow().timer_500_called, 0); |
580 | i_slint_core::tests::slint_mock_elapsed_time(200); // total: 500 |
581 | assert_eq!(state.borrow().timer_200_called, 2); |
582 | assert_eq!(state.borrow().timer_once_called, 1); |
583 | assert_eq!(state.borrow().timer_500_called, 1); |
584 | for _ in 0..10 { |
585 | i_slint_core::tests::slint_mock_elapsed_time(100); |
586 | } |
587 | // total: 1500 |
588 | assert_eq!(state.borrow().timer_200_called, 7); |
589 | assert_eq!(state.borrow().timer_once_called, 1); |
590 | assert_eq!(state.borrow().timer_500_called, 3); |
591 | state.borrow().timer_once.restart(); |
592 | state.borrow().timer_200.restart(); |
593 | state.borrow().timer_500.stop(); |
594 | slint::platform::update_timers_and_animations(); |
595 | i_slint_core::tests::slint_mock_elapsed_time(100); |
596 | assert_eq!(state.borrow().timer_200_called, 7); |
597 | assert_eq!(state.borrow().timer_once_called, 1); |
598 | assert_eq!(state.borrow().timer_500_called, 3); |
599 | slint::platform::update_timers_and_animations(); |
600 | i_slint_core::tests::slint_mock_elapsed_time(100); |
601 | assert_eq!(state.borrow().timer_200_called, 8); |
602 | assert_eq!(state.borrow().timer_once_called, 1); |
603 | assert_eq!(state.borrow().timer_500_called, 3); |
604 | slint::platform::update_timers_and_animations(); |
605 | i_slint_core::tests::slint_mock_elapsed_time(100); |
606 | assert_eq!(state.borrow().timer_200_called, 8); |
607 | assert_eq!(state.borrow().timer_once_called, 2); |
608 | assert_eq!(state.borrow().timer_500_called, 3); |
609 | slint::platform::update_timers_and_animations(); |
610 | i_slint_core::tests::slint_mock_elapsed_time(1000); |
611 | slint::platform::update_timers_and_animations(); |
612 | slint::platform::update_timers_and_animations(); |
613 | // Despite 1000ms have passed, the 200 timer is only called once because we didn't call update_timers_and_animations in between |
614 | assert_eq!(state.borrow().timer_200_called, 9); |
615 | assert_eq!(state.borrow().timer_once_called, 2); |
616 | assert_eq!(state.borrow().timer_500_called, 3); |
617 | let state_ = state.clone(); |
618 | state.borrow().timer_200.start(TimerMode::SingleShot, Duration::from_millis(200), move || { |
619 | state_.borrow_mut().timer_200_called += 1; |
620 | }); |
621 | for _ in 0..5 { |
622 | i_slint_core::tests::slint_mock_elapsed_time(75); |
623 | } |
624 | assert_eq!(state.borrow().timer_200_called, 10); |
625 | assert_eq!(state.borrow().timer_once_called, 2); |
626 | assert_eq!(state.borrow().timer_500_called, 3); |
627 | state.borrow().timer_200.restart(); |
628 | for _ in 0..5 { |
629 | i_slint_core::tests::slint_mock_elapsed_time(75); |
630 | } |
631 | assert_eq!(state.borrow().timer_200_called, 11); |
632 | assert_eq!(state.borrow().timer_once_called, 2); |
633 | assert_eq!(state.borrow().timer_500_called, 3); |
634 | |
635 | // Test re-starting from a callback |
636 | let state_ = state.clone(); |
637 | state.borrow_mut().timer_500.start(TimerMode::Repeated, Duration::from_millis(500), move || { |
638 | state_.borrow_mut().timer_500_called += 1; |
639 | let state__ = state_.clone(); |
640 | state_.borrow_mut().timer_500.start(TimerMode::Repeated, Duration::from_millis(500), move || { |
641 | state__.borrow_mut().timer_500_called += 1000; |
642 | }); |
643 | let state__ = state_.clone(); |
644 | state_.borrow_mut().timer_200.start(TimerMode::Repeated, Duration::from_millis(200), move || { |
645 | state__.borrow_mut().timer_200_called += 1000; |
646 | }); |
647 | }); |
648 | for _ in 0..20 { |
649 | i_slint_core::tests::slint_mock_elapsed_time(100); |
650 | } |
651 | assert_eq!(state.borrow().timer_200_called, 7011); |
652 | assert_eq!(state.borrow().timer_once_called, 2); |
653 | assert_eq!(state.borrow().timer_500_called, 3004); |
654 | |
655 | // Test set interval |
656 | let state_ = state.clone(); |
657 | state.borrow_mut().timer_200.start(TimerMode::Repeated, Duration::from_millis(200), move || { |
658 | state_.borrow_mut().timer_200_called += 1; |
659 | }); |
660 | let state_ = state.clone(); |
661 | state.borrow_mut().timer_once.start(TimerMode::Repeated, Duration::from_millis(300), move || { |
662 | state_.borrow_mut().timer_once_called += 1; |
663 | state_.borrow().timer_once.stop(); |
664 | }); |
665 | let state_ = state.clone(); |
666 | state.borrow_mut().timer_500.start(TimerMode::Repeated, Duration::from_millis(500), move || { |
667 | state_.borrow_mut().timer_500_called += 1; |
668 | }); |
669 | |
670 | let state_ = state.clone(); |
671 | slint::platform::update_timers_and_animations(); |
672 | for _ in 0..5 { |
673 | i_slint_core::tests::slint_mock_elapsed_time(100); |
674 | } |
675 | slint::platform::update_timers_and_animations(); |
676 | assert_eq!(state.borrow().timer_200_called, 7013); |
677 | assert_eq!(state.borrow().timer_once_called, 3); |
678 | assert_eq!(state.borrow().timer_500_called, 3005); |
679 | |
680 | for _ in 0..20 { |
681 | state.borrow().timer_200.set_interval(Duration::from_millis(200 * 2)); |
682 | state.borrow().timer_once.set_interval(Duration::from_millis(300 * 2)); |
683 | state.borrow().timer_500.set_interval(Duration::from_millis(500 * 2)); |
684 | |
685 | assert_eq!(state.borrow().timer_200_called, 7013); |
686 | assert_eq!(state.borrow().timer_once_called, 3); |
687 | assert_eq!(state.borrow().timer_500_called, 3005); |
688 | |
689 | i_slint_core::tests::slint_mock_elapsed_time(100); |
690 | } |
691 | |
692 | slint::platform::update_timers_and_animations(); |
693 | for _ in 0..9 { |
694 | i_slint_core::tests::slint_mock_elapsed_time(100); |
695 | } |
696 | slint::platform::update_timers_and_animations(); |
697 | assert_eq!(state.borrow().timer_200_called, 7015); |
698 | assert_eq!(state.borrow().timer_once_called, 3); |
699 | assert_eq!(state.borrow().timer_500_called, 3006); |
700 | |
701 | state.borrow_mut().timer_once.restart(); |
702 | for _ in 0..4 { |
703 | i_slint_core::tests::slint_mock_elapsed_time(100); |
704 | } |
705 | assert_eq!(state.borrow().timer_once_called, 3); |
706 | for _ in 0..4 { |
707 | i_slint_core::tests::slint_mock_elapsed_time(100); |
708 | } |
709 | assert_eq!(state.borrow().timer_once_called, 4); |
710 | |
711 | ``` |
712 | */ |
713 | #[cfg (doctest)] |
714 | const _TIMER_TESTS: () = (); |
715 | |
716 | /** |
717 | * Test that deleting an active timer from a timer event works. |
718 | ```rust |
719 | // There is a 200 ms timer that increase variable1 |
720 | // after 500ms, that timer is destroyed by a single shot timer, |
721 | // and a new new timer increase variable2 |
722 | i_slint_backend_testing::init(); |
723 | use slint::{Timer, TimerMode}; |
724 | use std::{rc::Rc, cell::RefCell, time::Duration}; |
725 | #[derive(Default)] |
726 | struct SharedState { |
727 | repeated_timer: Timer, |
728 | variable1: usize, |
729 | variable2: usize, |
730 | } |
731 | let state = Rc::new(RefCell::new(SharedState::default())); |
732 | // Note: state will be leaked because of circular dependencies: don't do that in production |
733 | let state_ = state.clone(); |
734 | state.borrow_mut().repeated_timer.start(TimerMode::Repeated, Duration::from_millis(200), move || { |
735 | state_.borrow_mut().variable1 += 1; |
736 | }); |
737 | let state_ = state.clone(); |
738 | Timer::single_shot(Duration::from_millis(500), move || { |
739 | state_.borrow_mut().repeated_timer = Default::default(); |
740 | let state = state_.clone(); |
741 | state_.borrow_mut().repeated_timer.start(TimerMode::Repeated, Duration::from_millis(200), move || { |
742 | state.borrow_mut().variable2 += 1; |
743 | }) |
744 | } ); |
745 | i_slint_core::tests::slint_mock_elapsed_time(10); |
746 | assert_eq!(state.borrow().variable1, 0); |
747 | assert_eq!(state.borrow().variable2, 0); |
748 | i_slint_core::tests::slint_mock_elapsed_time(200); |
749 | assert_eq!(state.borrow().variable1, 1); |
750 | assert_eq!(state.borrow().variable2, 0); |
751 | i_slint_core::tests::slint_mock_elapsed_time(200); |
752 | assert_eq!(state.borrow().variable1, 2); |
753 | assert_eq!(state.borrow().variable2, 0); |
754 | i_slint_core::tests::slint_mock_elapsed_time(100); |
755 | // More than 500ms have elapsed, the single shot timer should have been activated, but that has no effect on variable 1 and 2 |
756 | // This should just restart the timer so that the next change should happen 200ms from now |
757 | assert_eq!(state.borrow().variable1, 2); |
758 | assert_eq!(state.borrow().variable2, 0); |
759 | i_slint_core::tests::slint_mock_elapsed_time(110); |
760 | assert_eq!(state.borrow().variable1, 2); |
761 | assert_eq!(state.borrow().variable2, 0); |
762 | i_slint_core::tests::slint_mock_elapsed_time(100); |
763 | assert_eq!(state.borrow().variable1, 2); |
764 | assert_eq!(state.borrow().variable2, 1); |
765 | i_slint_core::tests::slint_mock_elapsed_time(100); |
766 | assert_eq!(state.borrow().variable1, 2); |
767 | assert_eq!(state.borrow().variable2, 1); |
768 | i_slint_core::tests::slint_mock_elapsed_time(100); |
769 | assert_eq!(state.borrow().variable1, 2); |
770 | assert_eq!(state.borrow().variable2, 2); |
771 | ``` |
772 | */ |
773 | #[cfg (doctest)] |
774 | const _BUG3029: () = (); |
775 | |
776 | /** |
777 | * Test that starting a singleshot timer works |
778 | ```rust |
779 | // There is a 200 ms singleshot timer that increase variable1 |
780 | i_slint_backend_testing::init(); |
781 | use slint::{Timer, TimerMode}; |
782 | use std::{rc::Rc, cell::RefCell, time::Duration}; |
783 | #[derive(Default)] |
784 | struct SharedState { |
785 | variable1: usize, |
786 | } |
787 | let state = Rc::new(RefCell::new(SharedState::default())); |
788 | // Note: state will be leaked because of circular dependencies: don't do that in production |
789 | let state_ = state.clone(); |
790 | let timer = Timer::default(); |
791 | |
792 | timer.start(TimerMode::SingleShot, Duration::from_millis(200), move || { |
793 | state_.borrow_mut().variable1 += 1; |
794 | }); |
795 | |
796 | // Singleshot timer set up and run... |
797 | assert!(timer.running()); |
798 | i_slint_core::tests::slint_mock_elapsed_time(10); |
799 | assert!(timer.running()); |
800 | assert_eq!(state.borrow().variable1, 0); |
801 | i_slint_core::tests::slint_mock_elapsed_time(200); |
802 | assert_eq!(state.borrow().variable1, 1); |
803 | assert!(!timer.running()); |
804 | i_slint_core::tests::slint_mock_elapsed_time(200); |
805 | assert_eq!(state.borrow().variable1, 1); // It's singleshot, it only triggers once! |
806 | assert!(!timer.running()); |
807 | |
808 | // Restart a previously set up singleshot timer |
809 | timer.restart(); |
810 | assert!(timer.running()); |
811 | assert_eq!(state.borrow().variable1, 1); |
812 | i_slint_core::tests::slint_mock_elapsed_time(200); |
813 | assert_eq!(state.borrow().variable1, 2); |
814 | assert!(!timer.running()); |
815 | i_slint_core::tests::slint_mock_elapsed_time(200); |
816 | assert_eq!(state.borrow().variable1, 2); // It's singleshot, it only triggers once! |
817 | assert!(!timer.running()); |
818 | |
819 | // Stop a non-running singleshot timer |
820 | timer.stop(); |
821 | assert!(!timer.running()); |
822 | assert_eq!(state.borrow().variable1, 2); |
823 | i_slint_core::tests::slint_mock_elapsed_time(200); |
824 | assert_eq!(state.borrow().variable1, 2); |
825 | assert!(!timer.running()); |
826 | i_slint_core::tests::slint_mock_elapsed_time(200); |
827 | assert_eq!(state.borrow().variable1, 2); // It's singleshot, it only triggers once! |
828 | assert!(!timer.running()); |
829 | |
830 | // Stop a running singleshot timer |
831 | timer.restart(); |
832 | assert!(timer.running()); |
833 | assert_eq!(state.borrow().variable1, 2); |
834 | i_slint_core::tests::slint_mock_elapsed_time(10); |
835 | timer.stop(); |
836 | assert!(!timer.running()); |
837 | i_slint_core::tests::slint_mock_elapsed_time(200); |
838 | assert_eq!(state.borrow().variable1, 2); |
839 | assert!(!timer.running()); |
840 | i_slint_core::tests::slint_mock_elapsed_time(200); |
841 | assert_eq!(state.borrow().variable1, 2); // It's singleshot, it only triggers once! |
842 | assert!(!timer.running()); |
843 | |
844 | // set_interval on a non-running singleshot timer |
845 | timer.set_interval(Duration::from_millis(300)); |
846 | assert!(!timer.running()); |
847 | i_slint_core::tests::slint_mock_elapsed_time(1000); |
848 | assert_eq!(state.borrow().variable1, 2); |
849 | assert!(!timer.running()); |
850 | timer.restart(); |
851 | assert!(timer.running()); |
852 | i_slint_core::tests::slint_mock_elapsed_time(200); |
853 | assert_eq!(state.borrow().variable1, 2); |
854 | assert!(timer.running()); |
855 | i_slint_core::tests::slint_mock_elapsed_time(200); |
856 | assert_eq!(state.borrow().variable1, 3); |
857 | assert!(!timer.running()); |
858 | i_slint_core::tests::slint_mock_elapsed_time(300); |
859 | assert_eq!(state.borrow().variable1, 3); // It's singleshot, it only triggers once! |
860 | assert!(!timer.running()); |
861 | |
862 | // set_interval on a running singleshot timer |
863 | timer.restart(); |
864 | assert!(timer.running()); |
865 | assert_eq!(state.borrow().variable1, 3); |
866 | i_slint_core::tests::slint_mock_elapsed_time(290); |
867 | timer.set_interval(Duration::from_millis(400)); |
868 | assert!(timer.running()); |
869 | i_slint_core::tests::slint_mock_elapsed_time(200); |
870 | assert_eq!(state.borrow().variable1, 3); |
871 | assert!(timer.running()); |
872 | i_slint_core::tests::slint_mock_elapsed_time(250); |
873 | assert_eq!(state.borrow().variable1, 4); |
874 | assert!(!timer.running()); |
875 | i_slint_core::tests::slint_mock_elapsed_time(400); |
876 | assert_eq!(state.borrow().variable1, 4); // It's singleshot, it only triggers once! |
877 | assert!(!timer.running()); |
878 | ``` |
879 | */ |
880 | #[cfg (doctest)] |
881 | const _SINGLESHOT_START: () = (); |
882 | |