1use crate::{
2 executor,
3 geometry::vector::Vector2F,
4 platform::{self, Event},
5};
6use anyhow::{anyhow, Result};
7use cocoa::{
8 appkit::{
9 NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable,
10 NSWindow, NSWindowStyleMask,
11 },
12 base::{id, nil},
13 foundation::{NSAutoreleasePool, NSSize, NSString},
14};
15use ctor::ctor;
16use objc::{
17 class,
18 declare::ClassDecl,
19 msg_send,
20 runtime::{Class, Object, Sel, BOOL, NO, YES},
21 sel, sel_impl,
22};
23use smol::Timer;
24use std::{
25 cell::{Cell, RefCell},
26 ffi::c_void,
27 mem, ptr,
28 rc::Rc,
29 time::{Duration, Instant},
30};
31
32use super::geometry::RectFExt;
33
34const WINDOW_STATE_IVAR: &'static str = "windowState";
35
36static mut WINDOW_CLASS: *const Class = ptr::null();
37static mut VIEW_CLASS: *const Class = ptr::null();
38static mut DELEGATE_CLASS: *const Class = ptr::null();
39
40#[ctor]
41unsafe fn build_classes() {
42 WINDOW_CLASS = {
43 let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
44 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
45 decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
46 decl.add_method(
47 sel!(canBecomeMainWindow),
48 yes as extern "C" fn(&Object, Sel) -> BOOL,
49 );
50 decl.add_method(
51 sel!(canBecomeKeyWindow),
52 yes as extern "C" fn(&Object, Sel) -> BOOL,
53 );
54 decl.add_method(
55 sel!(sendEvent:),
56 send_event as extern "C" fn(&Object, Sel, id),
57 );
58 decl.register()
59 };
60
61 VIEW_CLASS = {
62 let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
63 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
64 decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
65 decl.add_method(
66 sel!(keyDown:),
67 handle_view_event as extern "C" fn(&Object, Sel, id),
68 );
69 decl.add_method(
70 sel!(mouseDown:),
71 handle_view_event as extern "C" fn(&Object, Sel, id),
72 );
73 decl.add_method(
74 sel!(mouseUp:),
75 handle_view_event as extern "C" fn(&Object, Sel, id),
76 );
77 decl.add_method(
78 sel!(mouseDragged:),
79 handle_view_event as extern "C" fn(&Object, Sel, id),
80 );
81 decl.add_method(
82 sel!(scrollWheel:),
83 handle_view_event as extern "C" fn(&Object, Sel, id),
84 );
85 decl.register()
86 };
87
88 DELEGATE_CLASS = {
89 let mut decl = ClassDecl::new("GPUIWindowDelegate", class!(NSObject)).unwrap();
90 decl.add_method(
91 sel!(dealloc),
92 dealloc_delegate as extern "C" fn(&Object, Sel),
93 );
94 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
95 decl.add_method(
96 sel!(windowDidResize:),
97 window_did_resize as extern "C" fn(&Object, Sel, id),
98 );
99 decl.register()
100 };
101}
102
103pub struct Window(Rc<WindowState>);
104
105struct WindowState {
106 native_window: id,
107 event_callback: RefCell<Option<Box<dyn FnMut(Event) -> bool>>>,
108 resize_callback: RefCell<Option<Box<dyn FnMut(NSSize, f64)>>>,
109 synthetic_drag_counter: Cell<usize>,
110 executor: Rc<executor::Foreground>,
111}
112
113impl Window {
114 pub fn open(
115 options: platform::WindowOptions,
116 executor: Rc<executor::Foreground>,
117 ) -> Result<Self> {
118 unsafe {
119 let pool = NSAutoreleasePool::new(nil);
120
121 let frame = options.bounds.to_ns_rect();
122 let style_mask = NSWindowStyleMask::NSClosableWindowMask
123 | NSWindowStyleMask::NSMiniaturizableWindowMask
124 | NSWindowStyleMask::NSResizableWindowMask
125 | NSWindowStyleMask::NSTitledWindowMask;
126
127 let native_window: id = msg_send![WINDOW_CLASS, alloc];
128 let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
129 frame,
130 style_mask,
131 NSBackingStoreBuffered,
132 NO,
133 );
134
135 if native_window == nil {
136 return Err(anyhow!("window returned nil from initializer"));
137 }
138
139 let delegate: id = msg_send![DELEGATE_CLASS, alloc];
140 let delegate = delegate.init();
141 if native_window == nil {
142 return Err(anyhow!("delegate returned nil from initializer"));
143 }
144 native_window.setDelegate_(delegate);
145
146 let native_view: id = msg_send![VIEW_CLASS, alloc];
147 let native_view = NSView::init(native_view);
148 if native_view == nil {
149 return Err(anyhow!("view return nil from initializer"));
150 }
151
152 let window = Self(Rc::new(WindowState {
153 native_window,
154 event_callback: RefCell::new(None),
155 resize_callback: RefCell::new(None),
156 synthetic_drag_counter: Cell::new(0),
157 executor,
158 }));
159
160 (*native_window).set_ivar(
161 WINDOW_STATE_IVAR,
162 Rc::into_raw(window.0.clone()) as *const c_void,
163 );
164 (*native_view).set_ivar(
165 WINDOW_STATE_IVAR,
166 Rc::into_raw(window.0.clone()) as *const c_void,
167 );
168 (*delegate).set_ivar(
169 WINDOW_STATE_IVAR,
170 Rc::into_raw(window.0.clone()) as *const c_void,
171 );
172
173 if let Some(title) = options.title.as_ref() {
174 native_window.setTitle_(NSString::alloc(nil).init_str(title));
175 }
176 native_window.setAcceptsMouseMovedEvents_(YES);
177
178 native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
179 native_view.setWantsBestResolutionOpenGLSurface_(YES);
180
181 // From winit crate: On Mojave, views automatically become layer-backed shortly after
182 // being added to a native_window. Changing the layer-backedness of a view breaks the
183 // association between the view and its associated OpenGL context. To work around this,
184 // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
185 // itself and break the association with its context.
186 native_view.setWantsLayer(YES);
187
188 native_view.layer().setBackgroundColor_(
189 msg_send![class!(NSColor), colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0],
190 );
191
192 native_window.setContentView_(native_view.autorelease());
193 native_window.makeFirstResponder_(native_view);
194
195 native_window.center();
196 native_window.makeKeyAndOrderFront_(nil);
197
198 pool.drain();
199
200 Ok(window)
201 }
202 }
203
204 pub fn zoom(&self) {
205 unsafe {
206 self.0.native_window.performZoom_(nil);
207 }
208 }
209
210 pub fn size(&self) -> NSSize {
211 self.0.size()
212 }
213
214 pub fn backing_scale_factor(&self) -> f64 {
215 self.0.backing_scale_factor()
216 }
217
218 pub fn on_event<F: 'static + FnMut(Event) -> bool>(&mut self, callback: F) {
219 *self.0.event_callback.borrow_mut() = Some(Box::new(callback));
220 }
221
222 pub fn on_resize<F: 'static + FnMut(NSSize, f64)>(&mut self, callback: F) {
223 *self.0.resize_callback.borrow_mut() = Some(Box::new(callback));
224 }
225}
226
227impl Drop for Window {
228 fn drop(&mut self) {
229 unsafe {
230 self.0.native_window.close();
231 let _: () = msg_send![self.0.native_window.delegate(), release];
232 }
233 }
234}
235
236impl platform::Window for Window {}
237
238impl WindowState {
239 fn size(&self) -> NSSize {
240 let view_frame = unsafe { NSView::frame(self.native_window.contentView()) };
241 view_frame.size
242 }
243
244 fn backing_scale_factor(&self) -> f64 {
245 unsafe {
246 let screen: id = msg_send![self.native_window, screen];
247 NSScreen::backingScaleFactor(screen)
248 }
249 }
250
251 fn next_synthetic_drag_id(&self) -> usize {
252 let next_id = self.synthetic_drag_counter.get() + 1;
253 self.synthetic_drag_counter.set(next_id);
254 next_id
255 }
256}
257
258unsafe fn window_state(object: &Object) -> Rc<WindowState> {
259 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
260 let rc1 = Rc::from_raw(raw as *mut WindowState);
261 let rc2 = rc1.clone();
262 mem::forget(rc1);
263 rc2
264}
265
266unsafe fn drop_window_state(object: &Object) {
267 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
268 Rc::from_raw(raw as *mut WindowState);
269}
270
271extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
272 YES
273}
274
275extern "C" fn dealloc_window(this: &Object, _: Sel) {
276 unsafe {
277 drop_window_state(this);
278 let () = msg_send![super(this, class!(NSWindow)), dealloc];
279 }
280}
281
282extern "C" fn dealloc_view(this: &Object, _: Sel) {
283 unsafe {
284 drop_window_state(this);
285 let () = msg_send![super(this, class!(NSView)), dealloc];
286 }
287}
288
289extern "C" fn dealloc_delegate(this: &Object, _: Sel) {
290 unsafe {
291 let raw: *mut c_void = *this.get_ivar(WINDOW_STATE_IVAR);
292 Rc::from_raw(raw as *mut WindowState);
293 let () = msg_send![super(this, class!(NSObject)), dealloc];
294 }
295}
296
297extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
298 let window = unsafe { window_state(this) };
299
300 let event = unsafe { Event::from_native(native_event, Some(window.size().height as f32)) };
301
302 if let Some(event) = event {
303 match event {
304 Event::LeftMouseDragged { position } => schedule_synthetic_drag(&window, position),
305 Event::LeftMouseUp { .. } => {
306 window.next_synthetic_drag_id();
307 }
308 _ => {}
309 }
310
311 if let Some(callback) = window.event_callback.borrow_mut().as_mut() {
312 if callback(event) {
313 return;
314 }
315 }
316 }
317}
318
319extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
320 unsafe {
321 let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
322 }
323}
324
325extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
326 let window = unsafe { window_state(this) };
327 let size = window.size();
328 let scale_factor = window.backing_scale_factor();
329 if let Some(callback) = window.resize_callback.borrow_mut().as_mut() {
330 callback(size, scale_factor);
331 }
332 drop(window);
333}
334
335fn schedule_synthetic_drag(window_state: &Rc<WindowState>, position: Vector2F) {
336 let drag_id = window_state.next_synthetic_drag_id();
337 let weak_window_state = Rc::downgrade(window_state);
338 let instant = Instant::now() + Duration::from_millis(16);
339 window_state
340 .executor
341 .spawn(async move {
342 Timer::at(instant).await;
343 if let Some(window_state) = weak_window_state.upgrade() {
344 if window_state.synthetic_drag_counter.get() == drag_id {
345 if let Some(callback) = window_state.event_callback.borrow_mut().as_mut() {
346 schedule_synthetic_drag(&window_state, position);
347 callback(Event::LeftMouseDragged { position });
348 }
349 }
350 }
351 })
352 .detach();
353}