1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//! Timer for delayed or periodic callbacks.
//!
//! Wraps [`QTimer`](https://doc.qt.io/qt-6/qtimer.html).
use crate::ffi;
use crate::signal::{self, SignalHandle};
/// A repeating or single-shot timer.
///
/// `Timer` fires a callback at a fixed interval. The timer **starts
/// immediately** on [`build`](Builder::build). Use [`stop`](Timer::stop)
/// to halt it, or let it drop to stop and clean up.
///
/// # Signals
///
/// | Method | Qt signal | When |
/// |---|---|---|
/// | [`Builder::on_timeout`] | `QTimer::timeout` | Every `interval_ms` milliseconds |
///
/// # Example
///
/// ```no_run
/// let timer = Timer::new(1000)
/// .on_timeout(|| println!("tick"))
/// .build();
/// // Prints "tick" every second
/// app.exec();
/// ```
pub struct Timer {
ptr: *mut ffi::QTimer,
signal_handles: Vec<SignalHandle>,
}
impl Timer {
/// Start building a new timer with the given interval in milliseconds.
///
/// Use [`Timer::single_shot`] for a one-shot timer.
pub fn new(interval_ms: i32) -> Builder {
Builder::new(interval_ms)
}
/// Create a one-shot timer using Qt's `QTimer::singleShot` static.
///
/// Fires `f` once after `interval_ms` milliseconds, then automatically
/// cleans up. No `Timer` object is returned — the callback owns itself.
///
/// ```no_run
/// Timer::single_shot(500, || println!("fired once!"));
/// ```
pub fn single_shot<F: Fn() + 'static>(interval_ms: i32, f: F) {
let handle = signal::leak_void(f);
// QTimer::singleShot takes ownership — the closure leaks intentionally.
// In a one-shot, the signal fires once and never again, so the leak
// is bounded (one allocation total).
unsafe { ffi::QTimer_singleShot(interval_ms, handle.token); }
}
/// Start (or restart) the timer.
pub fn start(&self, interval_ms: i32) {
debug_assert!(!self.ptr.is_null());
unsafe { ffi::QTimer_start(self.ptr, interval_ms); }
}
/// Stop the timer.
pub fn stop(&self) {
debug_assert!(!self.ptr.is_null());
unsafe { ffi::QTimer_stop(self.ptr); }
}
/// Returns `true` if the timer is currently running.
pub fn is_active(&self) -> bool {
debug_assert!(!self.ptr.is_null());
unsafe { ffi::QTimer_isActive(self.ptr) }
}
}
impl Drop for Timer {
fn drop(&mut self) {
if self.ptr.is_null() { return; }
// Stop the timer first so no more signals fire, then reclaim.
unsafe { ffi::QTimer_stop(self.ptr); }
for h in self.signal_handles.drain(..) {
unsafe { h.reclaim(); }
}
unsafe { ffi::QTimer_delete(self.ptr) };
self.ptr = std::ptr::null_mut();
}
}
// ============================================================
// Builder
// ============================================================
/// Builder for [`Timer`].
pub struct Builder {
interval_ms: i32,
on_timeout: Option<Box<dyn Fn()>>,
}
impl Builder {
fn new(interval_ms: i32) -> Self {
Self { interval_ms, on_timeout: None }
}
/// Set the callback fired every `interval_ms` milliseconds.
pub fn on_timeout<F: Fn() + 'static>(mut self, f: F) -> Self {
self.on_timeout = Some(Box::new(f));
self
}
/// Create the `QTimer`, connect the signal, start it, and return the
/// Rust wrapper.
pub fn build(self) -> Timer {
let ptr = unsafe { ffi::QTimer_new() };
debug_assert!(!ptr.is_null());
let mut timer = Timer { ptr, signal_handles: Vec::new() };
if let Some(f) = self.on_timeout {
let h = signal::leak_void(f);
unsafe { ffi::QTimer_onTimeout(ptr, h.token); }
timer.signal_handles.push(h);
}
unsafe { ffi::QTimer_start(ptr, self.interval_ms); }
timer
}
}