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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
//! Push-button widget with click callbacks.
//!
//! Wraps [`QPushButton`](https://doc.qt.io/qt-6/qpushbutton.html).
use cxx::let_cxx_string;
use crate::ffi;
use crate::signal;
use crate::widget::AsWidget;
/// A pressable button with an optional click callback.
///
/// `PushButton` uses a **builder pattern**: call [`PushButton::new`] to
/// obtain a [`Builder`], chain `.on_clicked(f)`, `.parent(w)`, then call
/// `.build()` or `.show()`.
///
/// # Signals
///
/// | Method | Qt signal | When |
/// |---|---|---|
/// | [`Builder::on_clicked`] | `QPushButton::clicked` | Button is pressed and released |
///
/// # Memory safety
///
/// Signal closures are stored on the heap and passed to C++ as `u64`
/// tokens. On [`Drop`]:
///
/// - **No Qt parent:** closures are reclaimed, then the C++ object is
/// deleted. Safe — no more signals can fire after deletion.
/// - **Has Qt parent:** all signals are disconnected first, then closures
/// are reclaimed. The C++ object is left alone (Qt deletes it).
///
/// # Example
///
/// ```no_run
/// use qtrs::PushButton;
///
/// let btn = PushButton::new("Click me")
/// .on_clicked(|| println!("clicked!"))
/// .build();
/// ```
pub struct PushButton {
ptr: *mut ffi::QPushButton,
has_parent: bool,
#[allow(dead_code)]
text: String,
signal_handles: Vec<crate::signal::SignalHandle>,
}
impl PushButton {
/// Start building a new `QPushButton`.
///
/// Returns a [`Builder`]. Chain configuration, then call `.build()`.
pub fn new(text: impl Into<String>) -> Builder {
Builder::new(text.into())
}
/// Get the current button text.
pub fn text(&self) -> &str {
&self.text
}
/// Update the button text at runtime.
///
/// This calls
/// [`QPushButton::setText`](https://doc.qt.io/qt-6/qabstractbutton.html#text-prop).
pub fn set_text(&mut self, text: impl Into<String>) {
debug_assert!(!self.ptr.is_null(), "PushButton::set_text on null pointer");
self.text = text.into();
let_cxx_string!(c_text = &self.text);
unsafe {
ffi::QPushButton_setText(self.ptr, &c_text);
}
}
/// Show this button.
///
/// Normally child widgets are shown automatically by their parent;
/// use this only for standalone buttons.
pub fn show(&self) {
debug_assert!(!self.ptr.is_null(), "PushButton::show on null pointer");
unsafe { ffi::QPushButton_show(self.ptr) };
}
/// Connect a click callback to an already-existing button.
///
/// This is the runtime equivalent of
/// [`Builder::on_clicked`] — useful when the button was loaded
/// from a `.ui` file rather than built in Rust.
pub fn connect_clicked<F: Fn()>(&mut self, f: F) {
debug_assert!(!self.ptr.is_null());
let handle = signal::leak_void(f);
unsafe { ffi::QPushButton_onClicked(self.ptr, handle.token); }
self.signal_handles.push(handle);
}
/// Wrap an existing `QPushButton*` obtained via `findChild`.
/// The button already has a Qt parent, so Drop will not delete it.
#[doc(hidden)]
pub(crate) fn from_raw(ptr: *mut ffi::QPushButton, text: &str) -> Self {
debug_assert!(!ptr.is_null());
Self { ptr, has_parent: true, text: text.to_string(), signal_handles: Vec::new() }
}
}
impl AsWidget for PushButton {
fn widget_ptr(&self) -> *mut ffi::QWidget {
debug_assert!(!self.ptr.is_null(), "PushButton::widget_ptr on null pointer");
unsafe { ffi::toQWidget_QPushButton(self.ptr) }
}
fn set_has_parent(&mut self) {
self.has_parent = true;
}
}
impl Drop for PushButton {
fn drop(&mut self) {
if self.ptr.is_null() { return; }
if self.has_parent {
// Disconnect all Qt signals so no more callbacks fire,
// then reclaim closures safely (no use-after-free).
unsafe { ffi::QWidget_disconnectAll(self.ptr as *mut _); }
for h in self.signal_handles.drain(..) {
unsafe { h.reclaim(); }
}
} else {
for h in self.signal_handles.drain(..) {
unsafe { h.reclaim(); }
}
unsafe { ffi::QPushButton_delete(self.ptr) };
}
self.ptr = std::ptr::null_mut();
}
}
// ============================================================
// Builder
// ============================================================
/// Builder for [`PushButton`].
///
/// Collects text, parent, and signal callbacks, then creates the C++
/// `QPushButton` (and connects signals) in [`build`](Self::build).
pub struct Builder {
text: String,
on_clicked: Option<Box<dyn Fn()>>,
parent: Option<*mut ffi::QWidget>,
}
impl Builder {
fn new(text: String) -> Self {
Self {
text,
on_clicked: None,
parent: None,
}
}
/// Set the click callback.
///
/// The closure will be called **each time** the button is clicked.
/// It is stored on the heap and reclaimed when the button is dropped
/// (only if the button has no Qt parent — see the [memory safety]
/// note on [`PushButton`]).
pub fn on_clicked<F: Fn() + 'static>(mut self, f: F) -> Self {
self.on_clicked = Some(Box::new(f));
self
}
/// Set the parent widget.
///
/// The parent manages this button's C++ lifetime. Do not drop the
/// parent before the button.
pub fn parent(mut self, parent: &dyn AsWidget) -> Self {
self.parent = Some(parent.widget_ptr());
self
}
/// Create the C++ `QPushButton`, connect signals, and return the
/// Rust wrapper.
///
/// This is the terminal method of the builder pattern.
pub fn build(self) -> PushButton {
let_cxx_string!(c_text = &self.text);
let ptr = unsafe {
ffi::QPushButton_new(
&c_text,
self.parent.unwrap_or(std::ptr::null_mut()),
)
};
assert!(!ptr.is_null(), "QPushButton_new returned null");
let has_parent = self.parent.is_some();
let mut signal_handles = Vec::new();
// Connect click signal if a callback was provided.
if let Some(cb) = self.on_clicked {
let handle = signal::leak_void(cb);
unsafe { ffi::QPushButton_onClicked(ptr, handle.token); }
signal_handles.push(handle);
}
PushButton {
ptr,
has_parent,
text: self.text,
signal_handles,
}
}
/// Build and immediately show the button.
pub fn show(self) -> PushButton {
let btn = self.build();
btn.show();
btn
}
}