winio_ui_app_kit/widgets/
button.rs1use compio_log::{error, info, warn};
2use inherit_methods_macro::inherit_methods;
3use objc2::{
4 DeclaredClass, MainThreadOnly, define_class, msg_send,
5 rc::{Allocated, Retained},
6 sel,
7};
8use objc2_app_kit::{
9 NSBezelStyle, NSButton, NSButtonType, NSControlStateValueOff, NSControlStateValueOn,
10 NSWorkspace,
11};
12use objc2_foundation::{MainThreadMarker, NSObject, NSString, NSURL};
13use winio_callback::Callback;
14use winio_handle::AsContainer;
15use winio_primitive::{Point, Size};
16
17use crate::{GlobalRuntime, Result, Widget, catch, from_nsstring};
18
19#[derive(Debug)]
20pub struct Button {
21 handle: Widget,
22 view: Retained<NSButton>,
23 delegate: Retained<ButtonDelegate>,
24}
25
26#[inherit_methods(from = "self.handle")]
27impl Button {
28 pub fn new(parent: impl AsContainer) -> Result<Self> {
29 let parent = parent.as_container();
30 let mtm = parent.as_app_kit().mtm();
31
32 catch(|| unsafe {
33 let view = NSButton::new(mtm);
34 let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
35
36 let delegate = ButtonDelegate::new(mtm);
37 view.setTarget(Some(&delegate));
38 view.setAction(Some(sel!(onAction)));
39
40 view.setBezelStyle(NSBezelStyle::FlexiblePush);
41 Ok(Self {
42 handle,
43 view,
44 delegate,
45 })
46 })
47 .flatten()
48 }
49
50 pub fn is_visible(&self) -> Result<bool>;
51
52 pub fn set_visible(&mut self, v: bool) -> Result<()>;
53
54 pub fn is_enabled(&self) -> Result<bool>;
55
56 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
57
58 pub fn preferred_size(&self) -> Result<Size>;
59
60 pub fn loc(&self) -> Result<Point>;
61
62 pub fn set_loc(&mut self, p: Point) -> Result<()>;
63
64 pub fn size(&self) -> Result<Size>;
65
66 pub fn set_size(&mut self, v: Size) -> Result<()>;
67
68 pub fn tooltip(&self) -> Result<String>;
69
70 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
71
72 pub fn text(&self) -> Result<String> {
73 catch(|| from_nsstring(&self.view.title()))
74 }
75
76 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
77 catch(|| self.view.setTitle(&NSString::from_str(s.as_ref())))
78 }
79
80 pub async fn wait_click(&self) {
81 self.delegate.ivars().action.wait().await
82 }
83}
84
85winio_handle::impl_as_widget!(Button, handle);
86
87#[derive(Debug)]
88pub struct CheckBox {
89 handle: Button,
90}
91
92#[inherit_methods(from = "self.handle")]
93impl CheckBox {
94 pub fn new(parent: impl AsContainer) -> Result<Self> {
95 let handle = Button::new(parent)?;
96 catch(|| {
97 handle.view.setButtonType(NSButtonType::Switch);
98 handle.view.setAllowsMixedState(false);
99 })?;
100 Ok(Self { handle })
101 }
102
103 pub fn is_visible(&self) -> Result<bool>;
104
105 pub fn set_visible(&mut self, v: bool) -> Result<()>;
106
107 pub fn is_enabled(&self) -> Result<bool>;
108
109 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
110
111 pub fn preferred_size(&self) -> Result<Size> {
112 let mut s = self.handle.preferred_size()?;
113 s.width += 4.0;
114 Ok(s)
115 }
116
117 pub fn loc(&self) -> Result<Point>;
118
119 pub fn set_loc(&mut self, p: Point) -> Result<()>;
120
121 pub fn size(&self) -> Result<Size>;
122
123 pub fn set_size(&mut self, v: Size) -> Result<()>;
124
125 pub fn tooltip(&self) -> Result<String>;
126
127 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
128
129 pub fn text(&self) -> Result<String>;
130
131 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
132
133 pub fn is_checked(&self) -> Result<bool> {
134 catch(|| self.handle.view.state() == NSControlStateValueOn)
135 }
136
137 pub fn set_checked(&mut self, v: bool) -> Result<()> {
138 catch(|| {
139 self.handle.view.setState(if v {
140 NSControlStateValueOn
141 } else {
142 NSControlStateValueOff
143 })
144 })
145 }
146
147 pub async fn wait_click(&self) {
148 self.handle.wait_click().await
149 }
150}
151
152winio_handle::impl_as_widget!(CheckBox, handle);
153
154#[derive(Debug)]
155pub struct RadioButton {
156 handle: Button,
157}
158
159#[inherit_methods(from = "self.handle")]
160impl RadioButton {
161 pub fn new(parent: impl AsContainer) -> Result<Self> {
162 let handle = Button::new(parent)?;
163 catch(|| {
164 handle.view.setButtonType(NSButtonType::Radio);
165 handle.view.setAllowsMixedState(false);
166 })?;
167 Ok(Self { handle })
168 }
169
170 pub fn is_visible(&self) -> Result<bool>;
171
172 pub fn set_visible(&mut self, v: bool) -> Result<()>;
173
174 pub fn is_enabled(&self) -> Result<bool>;
175
176 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
177
178 pub fn preferred_size(&self) -> Result<Size>;
179
180 pub fn loc(&self) -> Result<Point>;
181
182 pub fn set_loc(&mut self, p: Point) -> Result<()>;
183
184 pub fn size(&self) -> Result<Size>;
185
186 pub fn set_size(&mut self, v: Size) -> Result<()>;
187
188 pub fn tooltip(&self) -> Result<String>;
189
190 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
191
192 pub fn text(&self) -> Result<String>;
193
194 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
195
196 pub fn is_checked(&self) -> Result<bool> {
197 catch(|| self.handle.view.state() == NSControlStateValueOn)
198 }
199
200 pub fn set_checked(&mut self, v: bool) -> Result<()> {
201 catch(|| {
202 self.handle.view.setState(if v {
203 NSControlStateValueOn
204 } else {
205 NSControlStateValueOff
206 })
207 })
208 }
209
210 pub async fn wait_click(&self) {
211 self.handle.wait_click().await
212 }
213}
214
215winio_handle::impl_as_widget!(RadioButton, handle);
216
217#[derive(Debug)]
218pub struct LinkLabel {
219 handle: Button,
220 uri: String,
221}
222
223#[inherit_methods(from = "self.handle")]
224impl LinkLabel {
225 pub fn new(parent: impl AsContainer) -> Result<Self> {
226 let handle = Button::new(parent)?;
227 catch(|| {
228 handle.view.setBordered(false);
229 handle.view.setBezelStyle(NSBezelStyle::Badge);
230 })?;
231 Ok(Self {
232 handle,
233 uri: String::new(),
234 })
235 }
236
237 pub fn is_visible(&self) -> Result<bool>;
238
239 pub fn set_visible(&mut self, v: bool) -> Result<()>;
240
241 pub fn is_enabled(&self) -> Result<bool>;
242
243 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
244
245 pub fn preferred_size(&self) -> Result<Size>;
246
247 pub fn loc(&self) -> Result<Point>;
248
249 pub fn set_loc(&mut self, p: Point) -> Result<()>;
250
251 pub fn size(&self) -> Result<Size>;
252
253 pub fn set_size(&mut self, v: Size) -> Result<()>;
254
255 pub fn tooltip(&self) -> Result<String>;
256
257 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
258
259 pub fn text(&self) -> Result<String>;
260
261 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
262
263 pub fn uri(&self) -> Result<String> {
264 Ok(self.uri.clone())
265 }
266
267 pub fn set_uri(&mut self, uri: impl AsRef<str>) -> Result<()> {
268 self.uri = uri.as_ref().to_string();
269 Ok(())
270 }
271
272 pub async fn wait_click(&self) {
273 loop {
274 self.handle.wait_click().await;
275 if self.uri.is_empty() {
276 break;
277 } else {
278 if let Some(url) = NSURL::URLWithString(&NSString::from_str(&self.uri)) {
279 info!("Opening link: {}", self.uri);
280 let opened = NSWorkspace::sharedWorkspace().openURL(&url);
281 if !opened {
282 error!("Failed to open link: {}", self.uri);
283 }
284 } else {
285 warn!("Invalid URL: {}", self.uri);
286 }
287 }
288 }
289 }
290}
291
292winio_handle::impl_as_widget!(LinkLabel, handle);
293
294#[derive(Debug, Default)]
295struct ButtonDelegateIvars {
296 action: Callback,
297}
298
299define_class! {
300 #[unsafe(super(NSObject))]
301 #[name = "WinioButtonDelegate"]
302 #[ivars = ButtonDelegateIvars]
303 #[thread_kind = MainThreadOnly]
304 #[derive(Debug)]
305 struct ButtonDelegate;
306
307 #[allow(non_snake_case)]
308 impl ButtonDelegate {
309 #[unsafe(method_id(init))]
310 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
311 let this = this.set_ivars(ButtonDelegateIvars::default());
312 unsafe { msg_send![super(this), init] }
313 }
314
315 #[unsafe(method(onAction))]
316 unsafe fn onAction(&self) {
317 self.ivars().action.signal::<GlobalRuntime>(());
318 }
319 }
320}
321
322impl ButtonDelegate {
323 pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
324 unsafe { msg_send![mtm.alloc::<Self>(), init] }
325 }
326}