1use std::{
2 f64,
3 os::raw::c_void,
4 sync::{atomic::Ordering, Arc, Weak},
5};
6
7use cocoa::{
8 appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
9 base::{id, nil},
10 foundation::{NSAutoreleasePool, NSUInteger},
11};
12use objc::{
13 declare::ClassDecl,
14 runtime::{Class, Object, Sel, BOOL, NO, YES},
15};
16
17use crate::{
18 dpi::{LogicalPosition, LogicalSize},
19 event::{Event, ModifiersState, WindowEvent},
20 platform_impl::platform::{
21 app_state::AppState,
22 app_state::INTERRUPT_EVENT_LOOP_EXIT,
23 event::{EventProxy, EventWrapper},
24 util::{self, IdRef},
25 view::ViewState,
26 window::{get_window_id, UnownedWindow},
27 },
28 window::{Fullscreen, WindowId},
29};
30
31pub struct WindowDelegateState {
32 ns_window: IdRef, ns_view: IdRef, window: Weak<UnownedWindow>,
36
37 initial_fullscreen: bool,
43
44 previous_position: Option<(f64, f64)>,
46
47 previous_scale_factor: f64,
49}
50
51impl WindowDelegateState {
52 pub fn new(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> Self {
53 let scale_factor = window.scale_factor();
54 let mut delegate_state = WindowDelegateState {
55 ns_window: window.ns_window.clone(),
56 ns_view: window.ns_view.clone(),
57 window: Arc::downgrade(&window),
58 initial_fullscreen,
59 previous_position: None,
60 previous_scale_factor: scale_factor,
61 };
62
63 if scale_factor != 1.0 {
64 delegate_state.emit_static_scale_factor_changed_event();
65 }
66
67 delegate_state
68 }
69
70 fn with_window<F, T>(&mut self, callback: F) -> Option<T>
71 where
72 F: FnOnce(&UnownedWindow) -> T,
73 {
74 self.window.upgrade().map(|ref window| callback(window))
75 }
76
77 pub fn emit_event(&mut self, event: WindowEvent<'static>) {
78 let event = Event::WindowEvent {
79 window_id: WindowId(get_window_id(*self.ns_window)),
80 event,
81 };
82 AppState::queue_event(EventWrapper::StaticEvent(event));
83 }
84
85 pub fn emit_static_scale_factor_changed_event(&mut self) {
86 let scale_factor = self.get_scale_factor();
87 if scale_factor == self.previous_scale_factor {
88 return ();
89 };
90
91 self.previous_scale_factor = scale_factor;
92 let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
93 ns_window: IdRef::retain(*self.ns_window),
94 suggested_size: self.view_size(),
95 scale_factor,
96 });
97 AppState::queue_event(wrapper);
98 }
99
100 pub fn emit_resize_event(&mut self) {
101 let rect = unsafe { NSView::frame(*self.ns_view) };
102 let scale_factor = self.get_scale_factor();
103 let logical_size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
104 let size = logical_size.to_physical(scale_factor);
105 self.emit_event(WindowEvent::Resized(size));
106 }
107
108 fn emit_move_event(&mut self) {
109 let rect = unsafe { NSWindow::frame(*self.ns_window) };
110 let x = rect.origin.x as f64;
111 let y = util::bottom_left_to_top_left(rect);
112 let moved = self.previous_position != Some((x, y));
113 if moved {
114 self.previous_position = Some((x, y));
115 let scale_factor = self.get_scale_factor();
116 let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
117 self.emit_event(WindowEvent::Moved(physical_pos));
118 }
119 }
120
121 fn get_scale_factor(&self) -> f64 {
122 (unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64
123 }
124
125 fn view_size(&self) -> LogicalSize<f64> {
126 let ns_size = unsafe { NSView::frame(*self.ns_view).size };
127 LogicalSize::new(ns_size.width as f64, ns_size.height as f64)
128 }
129}
130
131pub fn new_delegate(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> IdRef {
132 let state = WindowDelegateState::new(window, initial_fullscreen);
133 unsafe {
134 let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
136 let delegate: id = msg_send![WINDOW_DELEGATE_CLASS.0, alloc];
137 IdRef::new(msg_send![delegate, initWithWinit: state_ptr])
138 }
139}
140
141struct WindowDelegateClass(*const Class);
142unsafe impl Send for WindowDelegateClass {}
143unsafe impl Sync for WindowDelegateClass {}
144
145lazy_static! {
146 static ref WINDOW_DELEGATE_CLASS: WindowDelegateClass = unsafe {
147 let superclass = class!(NSResponder);
148 let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap();
149
150 decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
151 decl.add_method(
152 sel!(initWithWinit:),
153 init_with_winit as extern "C" fn(&Object, Sel, *mut c_void) -> id,
154 );
155
156 decl.add_method(
157 sel!(windowShouldClose:),
158 window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
159 );
160 decl.add_method(
161 sel!(windowWillClose:),
162 window_will_close as extern "C" fn(&Object, Sel, id),
163 );
164 decl.add_method(
165 sel!(windowDidResize:),
166 window_did_resize as extern "C" fn(&Object, Sel, id),
167 );
168 decl.add_method(
169 sel!(windowDidMove:),
170 window_did_move as extern "C" fn(&Object, Sel, id),
171 );
172 decl.add_method(
173 sel!(windowDidChangeBackingProperties:),
174 window_did_change_backing_properties as extern "C" fn(&Object, Sel, id),
175 );
176 decl.add_method(
177 sel!(windowDidBecomeKey:),
178 window_did_become_key as extern "C" fn(&Object, Sel, id),
179 );
180 decl.add_method(
181 sel!(windowDidResignKey:),
182 window_did_resign_key as extern "C" fn(&Object, Sel, id),
183 );
184
185 decl.add_method(
186 sel!(draggingEntered:),
187 dragging_entered as extern "C" fn(&Object, Sel, id) -> BOOL,
188 );
189 decl.add_method(
190 sel!(prepareForDragOperation:),
191 prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
192 );
193 decl.add_method(
194 sel!(performDragOperation:),
195 perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
196 );
197 decl.add_method(
198 sel!(concludeDragOperation:),
199 conclude_drag_operation as extern "C" fn(&Object, Sel, id),
200 );
201 decl.add_method(
202 sel!(draggingExited:),
203 dragging_exited as extern "C" fn(&Object, Sel, id),
204 );
205
206 decl.add_method(
207 sel!(window:willUseFullScreenPresentationOptions:),
208 window_will_use_fullscreen_presentation_options
209 as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
210 );
211 decl.add_method(
212 sel!(windowDidEnterFullScreen:),
213 window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
214 );
215 decl.add_method(
216 sel!(windowWillEnterFullScreen:),
217 window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
218 );
219 decl.add_method(
220 sel!(windowDidExitFullScreen:),
221 window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id),
222 );
223 decl.add_method(
224 sel!(windowWillExitFullScreen:),
225 window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
226 );
227 decl.add_method(
228 sel!(windowDidFailToEnterFullScreen:),
229 window_did_fail_to_enter_fullscreen as extern "C" fn(&Object, Sel, id),
230 );
231
232 decl.add_ivar::<*mut c_void>("winitState");
233 WindowDelegateClass(decl.register())
234 };
235}
236
237fn with_state<F: FnOnce(&mut WindowDelegateState) -> T, T>(this: &Object, callback: F) {
240 let state_ptr = unsafe {
241 let state_ptr: *mut c_void = *this.get_ivar("winitState");
242 &mut *(state_ptr as *mut WindowDelegateState)
243 };
244 callback(state_ptr);
245}
246
247extern "C" fn dealloc(this: &Object, _sel: Sel) {
248 with_state(this, |state| unsafe {
249 Box::from_raw(state as *mut WindowDelegateState);
250 });
251}
252
253extern "C" fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id {
254 unsafe {
255 let this: id = msg_send![this, init];
256 if this != nil {
257 (*this).set_ivar("winitState", state);
258 with_state(&*this, |state| {
259 let () = msg_send![*state.ns_window, setDelegate: this];
260 });
261 }
262 this
263 }
264}
265
266extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
267 trace!("Triggered `windowShouldClose:`");
268 with_state(this, |state| state.emit_event(WindowEvent::CloseRequested));
269 trace!("Completed `windowShouldClose:`");
270 NO
271}
272
273extern "C" fn window_will_close(this: &Object, _: Sel, _: id) {
274 trace!("Triggered `windowWillClose:`");
275 with_state(this, |state| unsafe {
276 let pool = NSAutoreleasePool::new(nil);
278 let () = msg_send![*state.ns_window, setDelegate: nil];
281 pool.drain();
282 state.emit_event(WindowEvent::Destroyed);
283 });
284 trace!("Completed `windowWillClose:`");
285}
286
287extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
288 trace!("Triggered `windowDidResize:`");
289 with_state(this, |state| {
290 state.emit_resize_event();
291 state.emit_move_event();
292 });
293 trace!("Completed `windowDidResize:`");
294}
295
296extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
298 trace!("Triggered `windowDidMove:`");
299 with_state(this, |state| {
300 state.emit_move_event();
301 });
302 trace!("Completed `windowDidMove:`");
303}
304
305extern "C" fn window_did_change_backing_properties(this: &Object, _: Sel, _: id) {
306 trace!("Triggered `windowDidChangeBackingProperties:`");
307 with_state(this, |state| {
308 state.emit_static_scale_factor_changed_event();
309 });
310 trace!("Completed `windowDidChangeBackingProperties:`");
311}
312
313extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) {
314 trace!("Triggered `windowDidBecomeKey:`");
315 with_state(this, |state| {
316 state.emit_event(WindowEvent::Focused(true));
319 });
320 trace!("Completed `windowDidBecomeKey:`");
321}
322
323extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) {
324 trace!("Triggered `windowDidResignKey:`");
325 with_state(this, |state| {
326 let view_state: &mut ViewState = unsafe {
338 let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref");
339 let state_ptr: *mut c_void = *ns_view.get_ivar("winitState");
340 &mut *(state_ptr as *mut ViewState)
341 };
342
343 if !view_state.modifiers.is_empty() {
345 view_state.modifiers = ModifiersState::empty();
346 state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers));
347 }
348
349 state.emit_event(WindowEvent::Focused(false));
350 });
351 trace!("Completed `windowDidResignKey:`");
352}
353
354extern "C" fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL {
356 trace!("Triggered `draggingEntered:`");
357
358 use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration};
359 use std::path::PathBuf;
360
361 let pb: id = unsafe { msg_send![sender, draggingPasteboard] };
362 let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) };
363
364 for file in unsafe { filenames.iter() } {
365 use cocoa::foundation::NSString;
366 use std::ffi::CStr;
367
368 unsafe {
369 let f = NSString::UTF8String(file);
370 let path = CStr::from_ptr(f).to_string_lossy().into_owned();
371
372 with_state(this, |state| {
373 state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path)));
374 });
375 }
376 }
377
378 trace!("Completed `draggingEntered:`");
379 YES
380}
381
382extern "C" fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL {
384 trace!("Triggered `prepareForDragOperation:`");
385 trace!("Completed `prepareForDragOperation:`");
386 YES
387}
388
389extern "C" fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL {
391 trace!("Triggered `performDragOperation:`");
392
393 use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration};
394 use std::path::PathBuf;
395
396 let pb: id = unsafe { msg_send![sender, draggingPasteboard] };
397 let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) };
398
399 for file in unsafe { filenames.iter() } {
400 use cocoa::foundation::NSString;
401 use std::ffi::CStr;
402
403 unsafe {
404 let f = NSString::UTF8String(file);
405 let path = CStr::from_ptr(f).to_string_lossy().into_owned();
406
407 with_state(this, |state| {
408 state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path)));
409 });
410 }
411 }
412
413 trace!("Completed `performDragOperation:`");
414 YES
415}
416
417extern "C" fn conclude_drag_operation(_: &Object, _: Sel, _: id) {
419 trace!("Triggered `concludeDragOperation:`");
420 trace!("Completed `concludeDragOperation:`");
421}
422
423extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
425 trace!("Triggered `draggingExited:`");
426 with_state(this, |state| {
427 state.emit_event(WindowEvent::HoveredFileCancelled)
428 });
429 trace!("Completed `draggingExited:`");
430}
431
432extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
434 trace!("Triggered `windowWillEnterFullscreen:`");
435
436 INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst);
437
438 with_state(this, |state| {
439 state.with_window(|window| {
440 trace!("Locked shared state in `window_will_enter_fullscreen`");
441 let mut shared_state = window.shared_state.lock().unwrap();
442 shared_state.maximized = window.is_zoomed();
443 match shared_state.fullscreen {
444 Some(Fullscreen::Exclusive(_)) => (),
448 Some(Fullscreen::Borderless(_)) => (),
452 None => {
455 let current_monitor = Some(window.current_monitor_inner());
456 shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
457 }
458 }
459 shared_state.in_fullscreen_transition = true;
460 trace!("Unlocked shared state in `window_will_enter_fullscreen`");
461 })
462 });
463 trace!("Completed `windowWillEnterFullscreen:`");
464}
465
466extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
468 trace!("Triggered `windowWillExitFullScreen:`");
469
470 INTERRUPT_EVENT_LOOP_EXIT.store(true, Ordering::SeqCst);
471
472 with_state(this, |state| {
473 state.with_window(|window| {
474 trace!("Locked shared state in `window_will_exit_fullscreen`");
475 let mut shared_state = window.shared_state.lock().unwrap();
476 shared_state.in_fullscreen_transition = true;
477 trace!("Unlocked shared state in `window_will_exit_fullscreen`");
478 });
479 });
480 trace!("Completed `windowWillExitFullScreen:`");
481}
482
483extern "C" fn window_will_use_fullscreen_presentation_options(
484 _this: &Object,
485 _: Sel,
486 _: id,
487 _proposed_options: NSUInteger,
488) -> NSUInteger {
489 (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
498 | NSApplicationPresentationOptions::NSApplicationPresentationHideDock
499 | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
500 .bits()
501}
502
503extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
505 INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst);
506
507 trace!("Triggered `windowDidEnterFullscreen:`");
508 with_state(this, |state| {
509 state.initial_fullscreen = false;
510 state.with_window(|window| {
511 trace!("Locked shared state in `window_did_enter_fullscreen`");
512 let mut shared_state = window.shared_state.lock().unwrap();
513 shared_state.in_fullscreen_transition = false;
514 let target_fullscreen = shared_state.target_fullscreen.take();
515 trace!("Unlocked shared state in `window_did_enter_fullscreen`");
516 drop(shared_state);
517 if let Some(target_fullscreen) = target_fullscreen {
518 window.set_fullscreen(target_fullscreen);
519 }
520 });
521 });
522 trace!("Completed `windowDidEnterFullscreen:`");
523}
524
525extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
527 INTERRUPT_EVENT_LOOP_EXIT.store(false, Ordering::SeqCst);
528
529 trace!("Triggered `windowDidExitFullscreen:`");
530 with_state(this, |state| {
531 state.with_window(|window| {
532 window.restore_state_from_fullscreen();
533 trace!("Locked shared state in `window_did_exit_fullscreen`");
534 let mut shared_state = window.shared_state.lock().unwrap();
535 shared_state.in_fullscreen_transition = false;
536 let target_fullscreen = shared_state.target_fullscreen.take();
537 trace!("Unlocked shared state in `window_did_exit_fullscreen`");
538 drop(shared_state);
539 if let Some(target_fullscreen) = target_fullscreen {
540 window.set_fullscreen(target_fullscreen);
541 }
542 })
543 });
544 trace!("Completed `windowDidExitFullscreen:`");
545}
546
547extern "C" fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) {
564 trace!("Triggered `windowDidFailToEnterFullscreen:`");
565 with_state(this, |state| {
566 state.with_window(|window| {
567 trace!("Locked shared state in `window_did_fail_to_enter_fullscreen`");
568 let mut shared_state = window.shared_state.lock().unwrap();
569 shared_state.in_fullscreen_transition = false;
570 shared_state.target_fullscreen = None;
571 trace!("Unlocked shared state in `window_did_fail_to_enter_fullscreen`");
572 });
573 if state.initial_fullscreen {
574 let _: () = unsafe {
575 msg_send![*state.ns_window,
576 performSelector:sel!(toggleFullScreen:)
577 withObject:nil
578 afterDelay: 0.5
579 ]
580 };
581 } else {
582 state.with_window(|window| window.restore_state_from_fullscreen());
583 }
584 });
585 trace!("Completed `windowDidFailToEnterFullscreen:`");
586}