winio_ui_app_kit/widgets/
window.rs1use inherit_methods_macro::inherit_methods;
2use objc2::{
3 DeclaredClass, MainThreadOnly, define_class, msg_send,
4 rc::{Allocated, Retained, Weak},
5 runtime::ProtocolObject,
6 sel,
7};
8use objc2_app_kit::{
9 NSAppKitVersionNumber, NSAppKitVersionNumber10_10, NSAppKitVersionNumber10_11,
10 NSAppKitVersionNumber10_14, NSAutoresizingMaskOptions, NSBackingStoreType, NSControl, NSScreen,
11 NSView, NSVisualEffectBlendingMode, NSVisualEffectMaterial, NSVisualEffectState,
12 NSVisualEffectView, NSWindow, NSWindowDelegate, NSWindowOrderingMode, NSWindowStyleMask,
13};
14use objc2_foundation::{
15 MainThreadMarker, NSDistributedNotificationCenter, NSNotification, NSObject, NSObjectProtocol,
16 NSPoint, NSRect, NSSize, NSString, ns_string,
17};
18use winio_callback::Callback;
19use winio_handle::{
20 AsContainer, AsWidget, AsWindow, BorrowedContainer, BorrowedWidget, BorrowedWindow,
21};
22use winio_primitive::{Point, Rect, Size};
23
24use crate::{
25 Error, GlobalRuntime, Result, catch, from_cgsize, from_nsstring, to_cgsize, transform_cgrect,
26 transform_rect,
27};
28
29#[derive(Debug)]
30pub struct Window {
31 wnd: Retained<NSWindow>,
32 content_view: Retained<NSView>,
33 delegate: Retained<WindowDelegate>,
34 vibrancy: Option<Vibrancy>,
35 vibrancy_view: Option<Retained<NSVisualEffectView>>,
36}
37
38impl Window {
39 pub fn new() -> Result<Self> {
40 unsafe {
41 let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread)?;
42
43 let frame = NSRect::new(NSPoint::ZERO, NSSize::new(100.0, 100.0));
44
45 let mut this = catch(|| {
46 let wnd = {
47 NSWindow::initWithContentRect_styleMask_backing_defer(
48 mtm.alloc(),
49 frame,
50 NSWindowStyleMask::Titled
51 | NSWindowStyleMask::Closable
52 | NSWindowStyleMask::Resizable
53 | NSWindowStyleMask::Miniaturizable,
54 NSBackingStoreType::Buffered,
55 false,
56 )
57 };
58
59 let delegate = WindowDelegate::new(mtm);
60 let del_obj = ProtocolObject::from_ref(&*delegate);
61 wnd.setDelegate(Some(del_obj));
62 wnd.setAcceptsMouseMovedEvents(true);
63 wnd.makeKeyWindow();
64
65 NSDistributedNotificationCenter::defaultCenter().addObserver_selector_name_object(
66 &delegate,
67 sel!(userDefaultsDidChange),
68 Some(ns_string!("AppleInterfaceThemeChangedNotification")),
69 None,
70 );
71
72 let content_view = wnd.contentView().ok_or(Error::NullPointer)?.clone();
73
74 Ok(Self {
75 wnd,
76 content_view,
77 delegate,
78 vibrancy: None,
79 vibrancy_view: None,
80 })
81 })
82 .flatten()?;
83 this.set_loc(Point::zero())?;
84 Ok(this)
85 }
86 }
87
88 fn screen(&self) -> Result<Retained<NSScreen>> {
89 catch(|| self.wnd.screen())?.ok_or(Error::NullPointer)
90 }
91
92 pub fn is_visible(&self) -> Result<bool> {
93 catch(|| self.wnd.isVisible())
94 }
95
96 pub fn set_visible(&mut self, v: bool) -> Result<()> {
97 catch(|| self.wnd.setIsVisible(v))
98 }
99
100 pub fn loc(&self) -> Result<Point> {
101 catch(|| {
102 let frame = self.wnd.frame();
103 let screen_frame = self.screen()?.frame();
104 Ok(transform_cgrect(from_cgsize(screen_frame.size), frame).origin)
105 })
106 .flatten()
107 }
108
109 pub fn set_loc(&mut self, p: Point) -> Result<()> {
110 catch(|| {
111 let frame = self.wnd.frame();
112 let screen_frame = self.screen()?.frame();
113 let frame = transform_rect(
114 from_cgsize(screen_frame.size),
115 Rect::new(p, from_cgsize(frame.size)),
116 );
117 self.wnd.setFrame_display(frame, true);
118 Ok(())
119 })
120 .flatten()
121 }
122
123 pub fn size(&self) -> Result<Size> {
124 catch(|| from_cgsize(self.wnd.frame().size))
125 }
126
127 pub fn set_size(&mut self, v: Size) -> Result<()> {
128 catch(|| {
129 let mut frame = self.wnd.frame();
130 let ydiff = v.height - frame.size.height;
131 frame.size = to_cgsize(v);
132 frame.origin.y -= ydiff;
133 self.wnd.setFrame_display(frame, true);
134 })
135 }
136
137 pub fn client_size(&self) -> Result<Size> {
138 catch(|| from_cgsize(self.content_view.frame().size))
139 }
140
141 pub fn text(&self) -> Result<String> {
142 catch(|| from_nsstring(&self.wnd.title()))
143 }
144
145 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
146 catch(|| self.wnd.setTitle(&NSString::from_str(s.as_ref())))
147 }
148
149 pub fn vibrancy(&self) -> Result<Option<Vibrancy>> {
150 Ok(self.vibrancy)
151 }
152
153 pub fn set_vibrancy(&mut self, v: Option<Vibrancy>) -> Result<()> {
154 unsafe {
155 if self.vibrancy == v {
156 return Ok(());
157 }
158 if NSAppKitVersionNumber < NSAppKitVersionNumber10_10 {
159 return Err(Error::NotSupported);
160 }
161 self.vibrancy = v;
162
163 catch(|| {
164 if let Some(v) = v {
165 let view = &self.content_view;
166 let bounds = view.bounds();
167 let vev: Retained<NSVisualEffectView> = NSVisualEffectView::initWithFrame(
168 self.wnd.mtm().alloc::<NSVisualEffectView>(),
169 bounds,
170 );
171 #[allow(deprecated)]
172 let m = if (v as u32 > 9 && NSAppKitVersionNumber < NSAppKitVersionNumber10_14)
173 || (v as u32 > 4 && NSAppKitVersionNumber < NSAppKitVersionNumber10_11)
174 {
175 NSVisualEffectMaterial::AppearanceBased
176 } else {
177 NSVisualEffectMaterial(v as u64 as _)
178 };
179 vev.setMaterial(m);
180 vev.setBlendingMode(NSVisualEffectBlendingMode::BehindWindow);
181 vev.setState(NSVisualEffectState::FollowsWindowActiveState);
182 vev.setAutoresizingMask(
183 NSAutoresizingMaskOptions::ViewWidthSizable
184 | NSAutoresizingMaskOptions::ViewHeightSizable,
185 );
186 view.addSubview_positioned_relativeTo(&vev, NSWindowOrderingMode::Below, None);
187 if let Some(vv) = self.vibrancy_view.replace(vev) {
188 vv.removeFromSuperview();
189 }
190 } else if let Some(vv) = self.vibrancy_view.take() {
191 vv.removeFromSuperview();
192 }
193 Ok(())
194 })
195 .flatten()
196 }
197 }
198
199 pub async fn wait_size(&self) {
200 self.delegate.ivars().did_resize.wait().await
201 }
202
203 pub async fn wait_move(&self) {
204 self.delegate.ivars().did_move.wait().await
205 }
206
207 pub async fn wait_close(&self) {
208 self.delegate.ivars().should_close.wait().await
209 }
210
211 pub async fn wait_theme_changed(&self) {
212 self.delegate.ivars().defaults_change.wait().await
213 }
214}
215
216impl AsWindow for Window {
217 fn as_window(&self) -> BorrowedWindow<'_> {
218 BorrowedWindow::app_kit(&self.wnd)
219 }
220}
221
222impl AsContainer for Window {
223 fn as_container(&self) -> BorrowedContainer<'_> {
224 BorrowedContainer::app_kit(&self.content_view)
225 }
226}
227
228impl Drop for Window {
229 fn drop(&mut self) {
230 unsafe {
231 NSDistributedNotificationCenter::defaultCenter().removeObserver(&self.delegate);
232 }
233 }
234}
235
236#[derive(Debug, Default)]
237struct WindowDelegateIvars {
238 did_resize: Callback,
239 did_move: Callback,
240 should_close: Callback,
241 defaults_change: Callback,
242}
243
244define_class! {
245 #[unsafe(super(NSObject))]
246 #[name = "WinioWindowDelegate"]
247 #[ivars = WindowDelegateIvars]
248 #[thread_kind = MainThreadOnly]
249 #[derive(Debug)]
250 struct WindowDelegate;
251
252 #[allow(non_snake_case)]
253 impl WindowDelegate {
254 #[unsafe(method_id(init))]
255 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
256 let this = this.set_ivars(WindowDelegateIvars::default());
257 unsafe { msg_send![super(this), init] }
258 }
259
260 #[unsafe(method(userDefaultsDidChange))]
261 unsafe fn userDefaultsDidChange(&self) {
262 self.ivars().defaults_change.signal::<GlobalRuntime>(());
263 }
264 }
265
266 unsafe impl NSObjectProtocol for WindowDelegate {}
267
268 #[allow(non_snake_case)]
269 unsafe impl NSWindowDelegate for WindowDelegate {
270 #[unsafe(method(windowDidResize:))]
271 unsafe fn windowDidResize(&self, _notification: &NSNotification) {
272 self.ivars().did_resize.signal::<GlobalRuntime>(());
273 }
274
275 #[unsafe(method(windowDidMove:))]
276 unsafe fn windowDidMove(&self, _notification: &NSNotification) {
277 self.ivars().did_move.signal::<GlobalRuntime>(());
278 }
279
280 #[unsafe(method(windowShouldClose:))]
281 unsafe fn windowShouldClose(&self, _sender: &NSWindow) -> bool {
282 self.ivars().should_close.signal::<GlobalRuntime>(())
283 }
284 }
285}
286
287impl WindowDelegate {
288 pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
289 unsafe { msg_send![mtm.alloc::<Self>(), init] }
290 }
291}
292
293#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
295#[non_exhaustive]
296pub enum Vibrancy {
297 #[deprecated(
298 note = "A default material appropriate for the view's effectiveAppearance. You should \
299 instead choose an appropriate semantic material."
300 )]
301 AppearanceBased = 0,
302 #[deprecated(note = "Use a semantic material instead.")]
303 Light = 1,
304 #[deprecated(note = "Use a semantic material instead.")]
305 Dark = 2,
306 #[deprecated(note = "Use a semantic material instead.")]
307 MediumLight = 8,
308 #[deprecated(note = "Use a semantic material instead.")]
309 UltraDark = 9,
310
311 Titlebar = 3,
313 Selection = 4,
315
316 Menu = 5,
318 Popover = 6,
320 Sidebar = 7,
322
323 HeaderView = 10,
325 Sheet = 11,
327 WindowBackground = 12,
329 HudWindow = 13,
331 FullScreenUI = 15,
333 Tooltip = 17,
335 ContentBackground = 18,
337 UnderWindowBackground = 21,
339 UnderPageBackground = 22,
341}
342
343#[derive(Debug)]
344pub(crate) struct Widget {
345 parent: Weak<NSView>,
346 view: Retained<NSView>,
347}
348
349impl Widget {
350 pub fn from_nsview(parent: impl AsContainer, view: Retained<NSView>) -> Result<Self> {
351 let mut this = catch(|| {
352 let parent = parent.as_container().as_app_kit();
353 parent.addSubview(&view);
354 Self {
355 parent: Weak::from_retained(parent),
356 view,
357 }
358 })?;
359 this.set_loc(Point::zero())?;
360 Ok(this)
361 }
362
363 pub fn parent(&self) -> Result<Retained<NSView>> {
364 catch(|| self.parent.load())?.ok_or(Error::NullPointer)
365 }
366
367 pub fn is_visible(&self) -> Result<bool> {
368 catch(|| !self.view.isHidden())
369 }
370
371 pub fn set_visible(&mut self, v: bool) -> Result<()> {
372 catch(|| self.view.setHidden(!v))
373 }
374
375 pub fn is_enabled(&self) -> Result<bool> {
376 catch(|| {
377 self.view
378 .downcast_ref::<NSControl>()
379 .map(|c| c.isEnabled())
380 .unwrap_or(true)
381 })
382 }
383
384 pub fn set_enabled(&mut self, v: bool) -> Result<()> {
385 catch(|| {
386 if let Some(c) = self.view.downcast_ref::<NSControl>() {
387 c.setEnabled(v);
388 }
389 })
390 }
391
392 pub fn preferred_size(&self) -> Result<Size> {
393 catch(|| {
394 let s = self.view.fittingSize();
395 if s != NSSize::ZERO {
396 return from_cgsize(s);
397 }
398 self.view
399 .downcast_ref::<NSControl>()
400 .map(|c| from_cgsize(c.sizeThatFits(NSSize::ZERO)))
401 .unwrap_or_default()
402 })
403 }
404
405 pub fn loc(&self) -> Result<Point> {
406 catch(|| {
407 let frame = self.view.frame();
408 let screen_frame = self.parent()?.frame();
409 Ok(transform_cgrect(from_cgsize(screen_frame.size), frame).origin)
410 })
411 .flatten()
412 }
413
414 pub fn set_loc(&mut self, p: Point) -> Result<()> {
415 catch(|| {
416 let frame = self.view.frame();
417 let screen_frame = self.parent()?.frame();
418 let frame = transform_rect(
419 from_cgsize(screen_frame.size),
420 Rect::new(p, from_cgsize(frame.size)),
421 );
422 self.view.setFrame(frame);
423 Ok(())
424 })
425 .flatten()
426 }
427
428 pub fn size(&self) -> Result<Size> {
429 catch(|| from_cgsize(self.view.frame().size))
430 }
431
432 pub fn set_size(&mut self, v: Size) -> Result<()> {
433 catch(|| {
434 let mut frame = self.view.frame();
435 let ydiff = v.height - frame.size.height;
436 frame.size = to_cgsize(v);
437 frame.origin.y -= ydiff;
438 self.view.setFrame(frame);
439 })
440 }
441
442 pub fn text(&self) -> Result<String> {
443 catch(|| {
444 self.view
445 .downcast_ref::<NSControl>()
446 .map(|c| from_nsstring(&c.stringValue()))
447 .unwrap_or_default()
448 })
449 }
450
451 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
452 catch(|| {
453 if let Some(c) = self.view.downcast_ref::<NSControl>() {
454 c.setStringValue(&NSString::from_str(s.as_ref()));
455 }
456 })
457 }
458
459 pub fn tooltip(&self) -> Result<String> {
460 catch(|| {
461 self.view
462 .toolTip()
463 .map(|s| from_nsstring(&s))
464 .unwrap_or_default()
465 })
466 }
467
468 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()> {
469 catch(|| {
470 let s = s.as_ref();
471 let s = if s.is_empty() {
472 None
473 } else {
474 Some(NSString::from_str(s))
475 };
476 self.view.setToolTip(s.as_deref());
477 })
478 }
479}
480
481impl Drop for Widget {
482 fn drop(&mut self) {
483 self.view.removeFromSuperview();
484 }
485}
486
487impl AsWidget for Widget {
488 fn as_widget(&self) -> BorrowedWidget<'_> {
489 BorrowedWidget::app_kit(&self.view)
490 }
491}
492
493impl AsContainer for Widget {
494 fn as_container(&self) -> BorrowedContainer<'_> {
495 BorrowedContainer::app_kit(&self.view)
496 }
497}
498
499#[derive(Debug)]
500pub struct View {
501 handle: Widget,
502}
503
504#[inherit_methods(from = "self.handle")]
505impl View {
506 pub fn new(parent: impl AsContainer) -> Result<Self> {
507 unsafe {
508 catch(|| {
509 let parent = parent.as_container();
510 let mtm = parent.as_app_kit().mtm();
511
512 let view = NSView::new(mtm);
513 let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
514
515 Ok(Self { handle })
516 })
517 .flatten()
518 }
519 }
520
521 pub fn is_visible(&self) -> Result<bool>;
522
523 pub fn set_visible(&mut self, v: bool) -> Result<()>;
524
525 pub fn loc(&self) -> Result<Point>;
526
527 pub fn set_loc(&mut self, p: Point) -> Result<()>;
528
529 pub fn size(&self) -> Result<Size>;
530
531 pub fn set_size(&mut self, v: Size) -> Result<()>;
532}
533
534winio_handle::impl_as_widget!(View, handle);
535winio_handle::impl_as_container!(View, handle);