1use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange};
2use crate::{
3 display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Executor, FileDropEvent,
4 GlobalPixels, InputEvent, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent,
5 MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
6 PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer,
7 WindowAppearance, WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
8};
9use block::ConcreteBlock;
10use cocoa::{
11 appkit::{
12 CGPoint, NSApplication, NSBackingStoreBuffered, NSFilenamesPboardType, NSPasteboard,
13 NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton,
14 NSWindowCollectionBehavior, NSWindowStyleMask, NSWindowTitleVisibility,
15 },
16 base::{id, nil},
17 foundation::{
18 NSArray, NSAutoreleasePool, NSDictionary, NSFastEnumeration, NSInteger, NSPoint, NSRect,
19 NSSize, NSString, NSUInteger,
20 },
21};
22use core_graphics::display::CGRect;
23use ctor::ctor;
24use foreign_types::ForeignTypeRef;
25use futures::channel::oneshot;
26use objc::{
27 class,
28 declare::ClassDecl,
29 msg_send,
30 runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
31 sel, sel_impl,
32};
33use parking_lot::Mutex;
34use std::{
35 any::Any,
36 cell::{Cell, RefCell},
37 ffi::{c_void, CStr},
38 mem,
39 ops::Range,
40 os::raw::c_char,
41 path::PathBuf,
42 ptr,
43 rc::Rc,
44 sync::{Arc, Weak},
45 time::Duration,
46};
47
48const WINDOW_STATE_IVAR: &str = "windowState";
49
50static mut WINDOW_CLASS: *const Class = ptr::null();
51static mut PANEL_CLASS: *const Class = ptr::null();
52static mut VIEW_CLASS: *const Class = ptr::null();
53
54#[allow(non_upper_case_globals)]
55const NSWindowStyleMaskNonactivatingPanel: NSWindowStyleMask =
56 unsafe { NSWindowStyleMask::from_bits_unchecked(1 << 7) };
57#[allow(non_upper_case_globals)]
58const NSNormalWindowLevel: NSInteger = 0;
59#[allow(non_upper_case_globals)]
60const NSPopUpWindowLevel: NSInteger = 101;
61#[allow(non_upper_case_globals)]
62const NSTrackingMouseEnteredAndExited: NSUInteger = 0x01;
63#[allow(non_upper_case_globals)]
64const NSTrackingMouseMoved: NSUInteger = 0x02;
65#[allow(non_upper_case_globals)]
66const NSTrackingActiveAlways: NSUInteger = 0x80;
67#[allow(non_upper_case_globals)]
68const NSTrackingInVisibleRect: NSUInteger = 0x200;
69#[allow(non_upper_case_globals)]
70const NSWindowAnimationBehaviorUtilityWindow: NSInteger = 4;
71#[allow(non_upper_case_globals)]
72const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
73// https://developer.apple.com/documentation/appkit/nsdragoperation
74#[allow(non_upper_case_globals)]
75type NSDragOperation = NSUInteger;
76#[allow(non_upper_case_globals)]
77const NSDragOperationNone: NSDragOperation = 0;
78#[allow(non_upper_case_globals)]
79const NSDragOperationCopy: NSDragOperation = 1;
80
81#[ctor]
82unsafe fn build_classes() {
83 WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow));
84 PANEL_CLASS = build_window_class("GPUIPanel", class!(NSPanel));
85 VIEW_CLASS = {
86 let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
87 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
88
89 decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
90
91 decl.add_method(
92 sel!(performKeyEquivalent:),
93 handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
94 );
95 decl.add_method(
96 sel!(keyDown:),
97 handle_key_down as extern "C" fn(&Object, Sel, id),
98 );
99 decl.add_method(
100 sel!(mouseDown:),
101 handle_view_event as extern "C" fn(&Object, Sel, id),
102 );
103 decl.add_method(
104 sel!(mouseUp:),
105 handle_view_event as extern "C" fn(&Object, Sel, id),
106 );
107 decl.add_method(
108 sel!(rightMouseDown:),
109 handle_view_event as extern "C" fn(&Object, Sel, id),
110 );
111 decl.add_method(
112 sel!(rightMouseUp:),
113 handle_view_event as extern "C" fn(&Object, Sel, id),
114 );
115 decl.add_method(
116 sel!(otherMouseDown:),
117 handle_view_event as extern "C" fn(&Object, Sel, id),
118 );
119 decl.add_method(
120 sel!(otherMouseUp:),
121 handle_view_event as extern "C" fn(&Object, Sel, id),
122 );
123 decl.add_method(
124 sel!(mouseMoved:),
125 handle_view_event as extern "C" fn(&Object, Sel, id),
126 );
127 decl.add_method(
128 sel!(mouseExited:),
129 handle_view_event as extern "C" fn(&Object, Sel, id),
130 );
131 decl.add_method(
132 sel!(mouseDragged:),
133 handle_view_event as extern "C" fn(&Object, Sel, id),
134 );
135 decl.add_method(
136 sel!(scrollWheel:),
137 handle_view_event as extern "C" fn(&Object, Sel, id),
138 );
139 decl.add_method(
140 sel!(flagsChanged:),
141 handle_view_event as extern "C" fn(&Object, Sel, id),
142 );
143 decl.add_method(
144 sel!(cancelOperation:),
145 cancel_operation as extern "C" fn(&Object, Sel, id),
146 );
147
148 decl.add_method(
149 sel!(makeBackingLayer),
150 make_backing_layer as extern "C" fn(&Object, Sel) -> id,
151 );
152
153 decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
154 decl.add_method(
155 sel!(viewDidChangeBackingProperties),
156 view_did_change_backing_properties as extern "C" fn(&Object, Sel),
157 );
158 decl.add_method(
159 sel!(setFrameSize:),
160 set_frame_size as extern "C" fn(&Object, Sel, NSSize),
161 );
162 decl.add_method(
163 sel!(displayLayer:),
164 display_layer as extern "C" fn(&Object, Sel, id),
165 );
166
167 decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
168 decl.add_method(
169 sel!(validAttributesForMarkedText),
170 valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
171 );
172 decl.add_method(
173 sel!(hasMarkedText),
174 has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
175 );
176 decl.add_method(
177 sel!(markedRange),
178 marked_range as extern "C" fn(&Object, Sel) -> NSRange,
179 );
180 decl.add_method(
181 sel!(selectedRange),
182 selected_range as extern "C" fn(&Object, Sel) -> NSRange,
183 );
184 decl.add_method(
185 sel!(firstRectForCharacterRange:actualRange:),
186 first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
187 );
188 decl.add_method(
189 sel!(insertText:replacementRange:),
190 insert_text as extern "C" fn(&Object, Sel, id, NSRange),
191 );
192 decl.add_method(
193 sel!(setMarkedText:selectedRange:replacementRange:),
194 set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
195 );
196 decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
197 decl.add_method(
198 sel!(attributedSubstringForProposedRange:actualRange:),
199 attributed_substring_for_proposed_range
200 as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
201 );
202 decl.add_method(
203 sel!(viewDidChangeEffectiveAppearance),
204 view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
205 );
206
207 // Suppress beep on keystrokes with modifier keys.
208 decl.add_method(
209 sel!(doCommandBySelector:),
210 do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
211 );
212
213 decl.add_method(
214 sel!(acceptsFirstMouse:),
215 accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
216 );
217
218 decl.register()
219 };
220}
221
222pub fn convert_mouse_position(position: NSPoint, window_height: Pixels) -> Point<Pixels> {
223 point(
224 px(position.x as f32),
225 // MacOS screen coordinates are relative to bottom left
226 window_height - px(position.y as f32),
227 )
228}
229
230unsafe fn build_window_class(name: &'static str, superclass: &Class) -> *const Class {
231 let mut decl = ClassDecl::new(name, superclass).unwrap();
232 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
233 decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
234 decl.add_method(
235 sel!(canBecomeMainWindow),
236 yes as extern "C" fn(&Object, Sel) -> BOOL,
237 );
238 decl.add_method(
239 sel!(canBecomeKeyWindow),
240 yes as extern "C" fn(&Object, Sel) -> BOOL,
241 );
242 decl.add_method(
243 sel!(windowDidResize:),
244 window_did_resize as extern "C" fn(&Object, Sel, id),
245 );
246 decl.add_method(
247 sel!(windowWillEnterFullScreen:),
248 window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
249 );
250 decl.add_method(
251 sel!(windowWillExitFullScreen:),
252 window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
253 );
254 decl.add_method(
255 sel!(windowDidMove:),
256 window_did_move as extern "C" fn(&Object, Sel, id),
257 );
258 decl.add_method(
259 sel!(windowDidBecomeKey:),
260 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
261 );
262 decl.add_method(
263 sel!(windowDidResignKey:),
264 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
265 );
266 decl.add_method(
267 sel!(windowShouldClose:),
268 window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
269 );
270 decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
271
272 decl.add_method(
273 sel!(draggingEntered:),
274 dragging_entered as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
275 );
276 decl.add_method(
277 sel!(draggingUpdated:),
278 dragging_updated as extern "C" fn(&Object, Sel, id) -> NSDragOperation,
279 );
280 decl.add_method(
281 sel!(draggingExited:),
282 dragging_exited as extern "C" fn(&Object, Sel, id),
283 );
284 decl.add_method(
285 sel!(performDragOperation:),
286 perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
287 );
288 decl.add_method(
289 sel!(concludeDragOperation:),
290 conclude_drag_operation as extern "C" fn(&Object, Sel, id),
291 );
292
293 decl.register()
294}
295
296///Used to track what the IME does when we send it a keystroke.
297///This is only used to handle the case where the IME mysteriously
298///swallows certain keys.
299///
300///Basically a direct copy of the approach that WezTerm uses in:
301///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs
302enum ImeState {
303 Continue,
304 Acted,
305 None,
306}
307
308struct InsertText {
309 replacement_range: Option<Range<usize>>,
310 text: String,
311}
312
313struct MacWindowState {
314 handle: AnyWindowHandle,
315 executor: Executor,
316 native_window: id,
317 renderer: MetalRenderer,
318 scene_to_render: Option<Scene>,
319 kind: WindowKind,
320 event_callback: Option<Box<dyn FnMut(InputEvent) -> bool>>,
321 activate_callback: Option<Box<dyn FnMut(bool)>>,
322 resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
323 fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
324 moved_callback: Option<Box<dyn FnMut()>>,
325 should_close_callback: Option<Box<dyn FnMut() -> bool>>,
326 close_callback: Option<Box<dyn FnOnce()>>,
327 appearance_changed_callback: Option<Box<dyn FnMut()>>,
328 input_handler: Option<Box<dyn PlatformInputHandler>>,
329 pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
330 last_key_equivalent: Option<KeyDownEvent>,
331 synthetic_drag_counter: usize,
332 last_fresh_keydown: Option<Keystroke>,
333 traffic_light_position: Option<Point<Pixels>>,
334 previous_modifiers_changed_event: Option<InputEvent>,
335 // State tracking what the IME did after the last request
336 ime_state: ImeState,
337 // Retains the last IME Text
338 ime_text: Option<String>,
339}
340
341impl MacWindowState {
342 fn move_traffic_light(&self) {
343 if let Some(traffic_light_position) = self.traffic_light_position {
344 let titlebar_height = self.titlebar_height();
345
346 unsafe {
347 let close_button: id = msg_send![
348 self.native_window,
349 standardWindowButton: NSWindowButton::NSWindowCloseButton
350 ];
351 let min_button: id = msg_send![
352 self.native_window,
353 standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
354 ];
355 let zoom_button: id = msg_send![
356 self.native_window,
357 standardWindowButton: NSWindowButton::NSWindowZoomButton
358 ];
359
360 let mut close_button_frame: CGRect = msg_send![close_button, frame];
361 let mut min_button_frame: CGRect = msg_send![min_button, frame];
362 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
363 let mut origin = point(
364 traffic_light_position.x,
365 titlebar_height
366 - traffic_light_position.y
367 - px(close_button_frame.size.height as f32),
368 );
369 let button_spacing =
370 px((min_button_frame.origin.x - close_button_frame.origin.x) as f32);
371
372 close_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
373 let _: () = msg_send![close_button, setFrame: close_button_frame];
374 origin.x += button_spacing;
375
376 min_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
377 let _: () = msg_send![min_button, setFrame: min_button_frame];
378 origin.x += button_spacing;
379
380 zoom_button_frame.origin = CGPoint::new(origin.x.into(), origin.y.into());
381 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
382 origin.x += button_spacing;
383 }
384 }
385 }
386
387 fn is_fullscreen(&self) -> bool {
388 unsafe {
389 let style_mask = self.native_window.styleMask();
390 style_mask.contains(NSWindowStyleMask::NSFullScreenWindowMask)
391 }
392 }
393
394 fn bounds(&self) -> WindowBounds {
395 unsafe {
396 if self.is_fullscreen() {
397 return WindowBounds::Fullscreen;
398 }
399
400 let frame = self.frame();
401 let screen_size = self.native_window.screen().visibleFrame().into();
402 if frame.size == screen_size {
403 WindowBounds::Maximized
404 } else {
405 WindowBounds::Fixed(frame)
406 }
407 }
408 }
409
410 fn frame(&self) -> Bounds<GlobalPixels> {
411 unsafe {
412 let frame = NSWindow::frame(self.native_window);
413 display_bounds_from_native(mem::transmute::<NSRect, CGRect>(frame))
414 }
415 }
416
417 fn content_size(&self) -> Size<Pixels> {
418 let NSSize { width, height, .. } =
419 unsafe { NSView::frame(self.native_window.contentView()) }.size;
420 size(px(width as f32), px(height as f32))
421 }
422
423 fn scale_factor(&self) -> f32 {
424 get_scale_factor(self.native_window)
425 }
426
427 fn titlebar_height(&self) -> Pixels {
428 unsafe {
429 let frame = NSWindow::frame(self.native_window);
430 let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
431 px((frame.size.height - content_layout_rect.size.height) as f32)
432 }
433 }
434
435 fn to_screen_ns_point(&self, point: Point<Pixels>) -> NSPoint {
436 unsafe {
437 let point = NSPoint::new(
438 point.x.into(),
439 (self.content_size().height - point.y).into(),
440 );
441 msg_send![self.native_window, convertPointToScreen: point]
442 }
443 }
444}
445
446unsafe impl Send for MacWindowState {}
447
448pub struct MacWindow(Arc<Mutex<MacWindowState>>);
449
450impl MacWindow {
451 pub fn open(handle: AnyWindowHandle, options: WindowOptions, executor: Executor) -> Self {
452 unsafe {
453 let pool = NSAutoreleasePool::new(nil);
454
455 let mut style_mask;
456 if let Some(titlebar) = options.titlebar.as_ref() {
457 style_mask = NSWindowStyleMask::NSClosableWindowMask
458 | NSWindowStyleMask::NSMiniaturizableWindowMask
459 | NSWindowStyleMask::NSResizableWindowMask
460 | NSWindowStyleMask::NSTitledWindowMask;
461
462 if titlebar.appears_transparent {
463 style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
464 }
465 } else {
466 style_mask = NSWindowStyleMask::NSTitledWindowMask
467 | NSWindowStyleMask::NSFullSizeContentViewWindowMask;
468 }
469
470 let native_window: id = match options.kind {
471 WindowKind::Normal => msg_send![WINDOW_CLASS, alloc],
472 WindowKind::PopUp => {
473 style_mask |= NSWindowStyleMaskNonactivatingPanel;
474 msg_send![PANEL_CLASS, alloc]
475 }
476 };
477
478 let display = options
479 .display_id
480 .and_then(|display_id| MacDisplay::all().find(|display| display.id() == display_id))
481 .unwrap_or_else(|| MacDisplay::primary());
482
483 let mut target_screen = nil;
484 let screens = NSScreen::screens(nil);
485 let count: u64 = cocoa::foundation::NSArray::count(screens);
486 for i in 0..count {
487 let screen = cocoa::foundation::NSArray::objectAtIndex(screens, i);
488 let device_description = NSScreen::deviceDescription(screen);
489 let screen_number_key: id = NSString::alloc(nil).init_str("NSScreenNumber");
490 let screen_number = device_description.objectForKey_(screen_number_key);
491 let screen_number: NSUInteger = msg_send![screen_number, unsignedIntegerValue];
492 if screen_number as u32 == display.id().0 {
493 target_screen = screen;
494 break;
495 }
496 }
497
498 let native_window = native_window.initWithContentRect_styleMask_backing_defer_screen_(
499 NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)),
500 style_mask,
501 NSBackingStoreBuffered,
502 NO,
503 target_screen,
504 );
505 assert!(!native_window.is_null());
506 let () = msg_send![
507 native_window,
508 registerForDraggedTypes:
509 NSArray::arrayWithObject(nil, NSFilenamesPboardType)
510 ];
511
512 let screen = native_window.screen();
513 match options.bounds {
514 WindowBounds::Fullscreen => {
515 native_window.toggleFullScreen_(nil);
516 }
517 WindowBounds::Maximized => {
518 native_window.setFrame_display_(screen.visibleFrame(), YES);
519 }
520 WindowBounds::Fixed(bounds) => {
521 let display_bounds = display.bounds();
522 let frame = if bounds.intersects(&display_bounds) {
523 display_bounds_to_native(bounds)
524 } else {
525 display_bounds_to_native(display_bounds)
526 };
527 native_window.setFrame_display_(mem::transmute::<CGRect, NSRect>(frame), YES);
528 }
529 }
530
531 let native_view: id = msg_send![VIEW_CLASS, alloc];
532 let native_view = NSView::init(native_view);
533
534 assert!(!native_view.is_null());
535
536 let window = Self(Arc::new(Mutex::new(MacWindowState {
537 handle,
538 executor,
539 native_window,
540 renderer: MetalRenderer::new(true),
541 scene_to_render: None,
542 kind: options.kind,
543 event_callback: None,
544 activate_callback: None,
545 resize_callback: None,
546 fullscreen_callback: None,
547 moved_callback: None,
548 should_close_callback: None,
549 close_callback: None,
550 appearance_changed_callback: None,
551 input_handler: None,
552 pending_key_down: None,
553 last_key_equivalent: None,
554 synthetic_drag_counter: 0,
555 last_fresh_keydown: None,
556 traffic_light_position: options
557 .titlebar
558 .as_ref()
559 .and_then(|titlebar| titlebar.traffic_light_position),
560 previous_modifiers_changed_event: None,
561 ime_state: ImeState::None,
562 ime_text: None,
563 })));
564
565 (*native_window).set_ivar(
566 WINDOW_STATE_IVAR,
567 Arc::into_raw(window.0.clone()) as *const c_void,
568 );
569 native_window.setDelegate_(native_window);
570 (*native_view).set_ivar(
571 WINDOW_STATE_IVAR,
572 Arc::into_raw(window.0.clone()) as *const c_void,
573 );
574
575 if let Some(title) = options
576 .titlebar
577 .as_ref()
578 .and_then(|t| t.title.as_ref().map(AsRef::as_ref))
579 {
580 native_window.setTitle_(NSString::alloc(nil).init_str(title));
581 }
582
583 native_window.setMovable_(options.is_movable as BOOL);
584
585 if options
586 .titlebar
587 .map_or(true, |titlebar| titlebar.appears_transparent)
588 {
589 native_window.setTitlebarAppearsTransparent_(YES);
590 native_window.setTitleVisibility_(NSWindowTitleVisibility::NSWindowTitleHidden);
591 }
592
593 native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
594 native_view.setWantsBestResolutionOpenGLSurface_(YES);
595
596 // From winit crate: On Mojave, views automatically become layer-backed shortly after
597 // being added to a native_window. Changing the layer-backedness of a view breaks the
598 // association between the view and its associated OpenGL context. To work around this,
599 // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
600 // itself and break the association with its context.
601 native_view.setWantsLayer(YES);
602 let _: () = msg_send![
603 native_view,
604 setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
605 ];
606
607 native_window.setContentView_(native_view.autorelease());
608 native_window.makeFirstResponder_(native_view);
609
610 if options.center {
611 native_window.center();
612 }
613
614 match options.kind {
615 WindowKind::Normal => {
616 native_window.setLevel_(NSNormalWindowLevel);
617 native_window.setAcceptsMouseMovedEvents_(YES);
618 }
619 WindowKind::PopUp => {
620 // Use a tracking area to allow receiving MouseMoved events even when
621 // the window or application aren't active, which is often the case
622 // e.g. for notification windows.
623 let tracking_area: id = msg_send![class!(NSTrackingArea), alloc];
624 let _: () = msg_send![
625 tracking_area,
626 initWithRect: NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.))
627 options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect
628 owner: native_view
629 userInfo: nil
630 ];
631 let _: () =
632 msg_send![native_view, addTrackingArea: tracking_area.autorelease()];
633
634 native_window.setLevel_(NSPopUpWindowLevel);
635 let _: () = msg_send![
636 native_window,
637 setAnimationBehavior: NSWindowAnimationBehaviorUtilityWindow
638 ];
639 native_window.setCollectionBehavior_(
640 NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces |
641 NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary
642 );
643 }
644 }
645 if options.focus {
646 native_window.makeKeyAndOrderFront_(nil);
647 } else if options.show {
648 native_window.orderFront_(nil);
649 }
650
651 window.0.lock().move_traffic_light();
652 pool.drain();
653
654 window
655 }
656 }
657
658 pub fn main_window() -> Option<AnyWindowHandle> {
659 unsafe {
660 let app = NSApplication::sharedApplication(nil);
661 let main_window: id = msg_send![app, mainWindow];
662 if msg_send![main_window, isKindOfClass: WINDOW_CLASS] {
663 let handle = get_window_state(&*main_window).lock().handle;
664 Some(handle)
665 } else {
666 None
667 }
668 }
669 }
670}
671
672impl Drop for MacWindow {
673 fn drop(&mut self) {
674 let this = self.0.clone();
675 let executor = self.0.lock().executor.clone();
676 executor
677 .run_on_main(move || unsafe {
678 this.lock().native_window.close();
679 })
680 .detach();
681 }
682}
683
684impl PlatformWindow for MacWindow {
685 fn bounds(&self) -> WindowBounds {
686 self.0.as_ref().lock().bounds()
687 }
688
689 fn content_size(&self) -> Size<Pixels> {
690 self.0.as_ref().lock().content_size().into()
691 }
692
693 fn scale_factor(&self) -> f32 {
694 self.0.as_ref().lock().scale_factor()
695 }
696
697 fn titlebar_height(&self) -> Pixels {
698 self.0.as_ref().lock().titlebar_height()
699 }
700
701 fn appearance(&self) -> WindowAppearance {
702 unsafe {
703 let appearance: id = msg_send![self.0.lock().native_window, effectiveAppearance];
704 WindowAppearance::from_native(appearance)
705 }
706 }
707
708 fn display(&self) -> Rc<dyn PlatformDisplay> {
709 unsafe {
710 let screen = self.0.lock().native_window.screen();
711 let device_description: id = msg_send![screen, deviceDescription];
712 let screen_number: id = NSDictionary::valueForKey_(
713 device_description,
714 NSString::alloc(nil).init_str("NSScreenNumber"),
715 );
716
717 let screen_number: u32 = msg_send![screen_number, unsignedIntValue];
718
719 Rc::new(MacDisplay(screen_number))
720 }
721 }
722
723 fn mouse_position(&self) -> Point<Pixels> {
724 let position = unsafe {
725 self.0
726 .lock()
727 .native_window
728 .mouseLocationOutsideOfEventStream()
729 };
730 convert_mouse_position(position, self.content_size().height)
731 }
732
733 fn as_any_mut(&mut self) -> &mut dyn Any {
734 self
735 }
736
737 fn set_input_handler(&mut self, input_handler: Box<dyn PlatformInputHandler>) {
738 self.0.as_ref().lock().input_handler = Some(input_handler);
739 }
740
741 fn prompt(
742 &self,
743 level: WindowPromptLevel,
744 msg: &str,
745 answers: &[&str],
746 ) -> oneshot::Receiver<usize> {
747 // macOs applies overrides to modal window buttons after they are added.
748 // Two most important for this logic are:
749 // * Buttons with "Cancel" title will be displayed as the last buttons in the modal
750 // * Last button added to the modal via `addButtonWithTitle` stays focused
751 // * Focused buttons react on "space"/" " keypresses
752 // * Usage of `keyEquivalent`, `makeFirstResponder` or `setInitialFirstResponder` does not change the focus
753 //
754 // See also https://developer.apple.com/documentation/appkit/nsalert/1524532-addbuttonwithtitle#discussion
755 // ```
756 // By default, the first button has a key equivalent of Return,
757 // any button with a title of “Cancel” has a key equivalent of Escape,
758 // and any button with the title “Don’t Save” has a key equivalent of Command-D (but only if it’s not the first button).
759 // ```
760 //
761 // To avoid situations when the last element added is "Cancel" and it gets the focus
762 // (hence stealing both ESC and Space shortcuts), we find and add one non-Cancel button
763 // last, so it gets focus and a Space shortcut.
764 // This way, "Save this file? Yes/No/Cancel"-ish modals will get all three buttons mapped with a key.
765 let latest_non_cancel_label = answers
766 .iter()
767 .enumerate()
768 .rev()
769 .find(|(_, &label)| label != "Cancel")
770 .filter(|&(label_index, _)| label_index > 0);
771
772 unsafe {
773 let alert: id = msg_send![class!(NSAlert), alloc];
774 let alert: id = msg_send![alert, init];
775 let alert_style = match level {
776 WindowPromptLevel::Info => 1,
777 WindowPromptLevel::Warning => 0,
778 WindowPromptLevel::Critical => 2,
779 };
780 let _: () = msg_send![alert, setAlertStyle: alert_style];
781 let _: () = msg_send![alert, setMessageText: ns_string(msg)];
782
783 for (ix, answer) in answers
784 .iter()
785 .enumerate()
786 .filter(|&(ix, _)| Some(ix) != latest_non_cancel_label.map(|(ix, _)| ix))
787 {
788 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
789 let _: () = msg_send![button, setTag: ix as NSInteger];
790 }
791 if let Some((ix, answer)) = latest_non_cancel_label {
792 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
793 let _: () = msg_send![button, setTag: ix as NSInteger];
794 }
795
796 let (done_tx, done_rx) = oneshot::channel();
797 let done_tx = Cell::new(Some(done_tx));
798 let block = ConcreteBlock::new(move |answer: NSInteger| {
799 if let Some(done_tx) = done_tx.take() {
800 let _ = done_tx.send(answer.try_into().unwrap());
801 }
802 });
803 let block = block.copy();
804 let native_window = self.0.lock().native_window;
805 let executor = self.0.lock().executor.clone();
806 executor
807 .spawn_on_main_local(async move {
808 let _: () = msg_send![
809 alert,
810 beginSheetModalForWindow: native_window
811 completionHandler: block
812 ];
813 })
814 .detach();
815
816 done_rx
817 }
818 }
819
820 fn activate(&self) {
821 let window = self.0.lock().native_window;
822 let executor = self.0.lock().executor.clone();
823 executor
824 .spawn_on_main_local(async move {
825 unsafe {
826 let _: () = msg_send![window, makeKeyAndOrderFront: nil];
827 }
828 })
829 .detach();
830 }
831
832 fn set_title(&mut self, title: &str) {
833 unsafe {
834 let app = NSApplication::sharedApplication(nil);
835 let window = self.0.lock().native_window;
836 let title = ns_string(title);
837 let _: () = msg_send![app, changeWindowsItem:window title:title filename:false];
838 let _: () = msg_send![window, setTitle: title];
839 self.0.lock().move_traffic_light();
840 }
841 }
842
843 fn set_edited(&mut self, edited: bool) {
844 unsafe {
845 let window = self.0.lock().native_window;
846 msg_send![window, setDocumentEdited: edited as BOOL]
847 }
848
849 // Changing the document edited state resets the traffic light position,
850 // so we have to move it again.
851 self.0.lock().move_traffic_light();
852 }
853
854 fn show_character_palette(&self) {
855 unsafe {
856 let app = NSApplication::sharedApplication(nil);
857 let window = self.0.lock().native_window;
858 let _: () = msg_send![app, orderFrontCharacterPalette: window];
859 }
860 }
861
862 fn minimize(&self) {
863 let window = self.0.lock().native_window;
864 unsafe {
865 window.miniaturize_(nil);
866 }
867 }
868
869 fn zoom(&self) {
870 let this = self.0.lock();
871 let window = this.native_window;
872 this.executor
873 .spawn_on_main_local(async move {
874 unsafe {
875 window.zoom_(nil);
876 }
877 })
878 .detach();
879 }
880
881 fn toggle_full_screen(&self) {
882 let this = self.0.lock();
883 let window = this.native_window;
884 this.executor
885 .spawn_on_main_local(async move {
886 unsafe {
887 window.toggleFullScreen_(nil);
888 }
889 })
890 .detach();
891 }
892
893 fn on_input(&self, callback: Box<dyn FnMut(InputEvent) -> bool>) {
894 self.0.as_ref().lock().event_callback = Some(callback);
895 }
896
897 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
898 self.0.as_ref().lock().activate_callback = Some(callback);
899 }
900
901 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
902 self.0.as_ref().lock().resize_callback = Some(callback);
903 }
904
905 fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
906 self.0.as_ref().lock().fullscreen_callback = Some(callback);
907 }
908
909 fn on_moved(&self, callback: Box<dyn FnMut()>) {
910 self.0.as_ref().lock().moved_callback = Some(callback);
911 }
912
913 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
914 self.0.as_ref().lock().should_close_callback = Some(callback);
915 }
916
917 fn on_close(&self, callback: Box<dyn FnOnce()>) {
918 self.0.as_ref().lock().close_callback = Some(callback);
919 }
920
921 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
922 self.0.lock().appearance_changed_callback = Some(callback);
923 }
924
925 fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
926 let self_borrow = self.0.lock();
927 let self_handle = self_borrow.handle;
928
929 unsafe {
930 let app = NSApplication::sharedApplication(nil);
931
932 // Convert back to screen coordinates
933 let screen_point = self_borrow.to_screen_ns_point(position);
934
935 let window_number: NSInteger = msg_send![class!(NSWindow), windowNumberAtPoint:screen_point belowWindowWithWindowNumber:0];
936 let top_most_window: id = msg_send![app, windowWithWindowNumber: window_number];
937
938 let is_panel: BOOL = msg_send![top_most_window, isKindOfClass: PANEL_CLASS];
939 let is_window: BOOL = msg_send![top_most_window, isKindOfClass: WINDOW_CLASS];
940 if is_panel == YES || is_window == YES {
941 let topmost_window = get_window_state(&*top_most_window).lock().handle;
942 topmost_window == self_handle
943 } else {
944 // Someone else's window is on top
945 false
946 }
947 }
948 }
949
950 fn draw(&self, scene: Scene) {
951 let mut this = self.0.lock();
952 this.scene_to_render = Some(scene);
953 unsafe {
954 let _: () = msg_send![this.native_window.contentView(), setNeedsDisplay: YES];
955 }
956 }
957
958 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
959 self.0.lock().renderer.sprite_atlas().clone()
960 }
961}
962
963fn get_scale_factor(native_window: id) -> f32 {
964 unsafe {
965 let screen: id = msg_send![native_window, screen];
966 NSScreen::backingScaleFactor(screen) as f32
967 }
968}
969
970unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
971 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
972 let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
973 let rc2 = rc1.clone();
974 mem::forget(rc1);
975 rc2
976}
977
978unsafe fn drop_window_state(object: &Object) {
979 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
980 Rc::from_raw(raw as *mut RefCell<MacWindowState>);
981}
982
983extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
984 YES
985}
986
987extern "C" fn dealloc_window(this: &Object, _: Sel) {
988 unsafe {
989 drop_window_state(this);
990 let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
991 }
992}
993
994extern "C" fn dealloc_view(this: &Object, _: Sel) {
995 unsafe {
996 drop_window_state(this);
997 let _: () = msg_send![super(this, class!(NSView)), dealloc];
998 }
999}
1000
1001extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
1002 handle_key_event(this, native_event, true)
1003}
1004
1005extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
1006 handle_key_event(this, native_event, false);
1007}
1008
1009extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
1010 let window_state = unsafe { get_window_state(this) };
1011 let mut lock = window_state.as_ref().lock();
1012
1013 let window_height = lock.content_size().height;
1014 let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
1015
1016 if let Some(InputEvent::KeyDown(event)) = event {
1017 // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
1018 // If that event isn't handled, it will then dispatch a "key down" event. GPUI
1019 // makes no distinction between these two types of events, so we need to ignore
1020 // the "key down" event if we've already just processed its "key equivalent" version.
1021 if key_equivalent {
1022 lock.last_key_equivalent = Some(event.clone());
1023 } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
1024 return NO;
1025 }
1026
1027 let keydown = event.keystroke.clone();
1028 let fn_modifier = keydown.modifiers.function;
1029 // Ignore events from held-down keys after some of the initially-pressed keys
1030 // were released.
1031 if event.is_held {
1032 if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
1033 return YES;
1034 }
1035 } else {
1036 lock.last_fresh_keydown = Some(keydown);
1037 }
1038 lock.pending_key_down = Some((event, None));
1039 drop(lock);
1040
1041 // Send the event to the input context for IME handling, unless the `fn` modifier is
1042 // being pressed.
1043 if !fn_modifier {
1044 unsafe {
1045 let input_context: id = msg_send![this, inputContext];
1046 let _: BOOL = msg_send![input_context, handleEvent: native_event];
1047 }
1048 }
1049
1050 let mut handled = false;
1051 let mut lock = window_state.lock();
1052 let ime_text = lock.ime_text.clone();
1053 if let Some((event, insert_text)) = lock.pending_key_down.take() {
1054 let is_held = event.is_held;
1055 if let Some(mut callback) = lock.event_callback.take() {
1056 drop(lock);
1057
1058 let is_composing =
1059 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1060 .flatten()
1061 .is_some();
1062 if !is_composing {
1063 // if the IME has changed the key, we'll first emit an event with the character
1064 // generated by the IME system; then fallback to the keystroke if that is not
1065 // handled.
1066 // cases that we have working:
1067 // - " on a brazillian layout by typing <quote><space>
1068 // - ctrl-` on a brazillian layout by typing <ctrl-`>
1069 // - $ on a czech QWERTY layout by typing <alt-4>
1070 // - 4 on a czech QWERTY layout by typing <shift-4>
1071 // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
1072 if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
1073 let event_with_ime_text = KeyDownEvent {
1074 is_held: false,
1075 keystroke: Keystroke {
1076 // we match ctrl because some use-cases need it.
1077 // we don't match alt because it's often used to generate the optional character
1078 // we don't match shift because we're not here with letters (usually)
1079 // we don't match cmd/fn because they don't seem to use IME
1080 modifiers: Default::default(),
1081 key: ime_text.clone().unwrap(),
1082 ime_key: None, // todo!("handle IME key")
1083 },
1084 };
1085 handled = callback(InputEvent::KeyDown(event_with_ime_text));
1086 }
1087 if !handled {
1088 // empty key happens when you type a deadkey in input composition.
1089 // (e.g. on a brazillian keyboard typing quote is a deadkey)
1090 if !event.keystroke.key.is_empty() {
1091 handled = callback(InputEvent::KeyDown(event));
1092 }
1093 }
1094 }
1095
1096 if !handled {
1097 if let Some(insert) = insert_text {
1098 handled = true;
1099 with_input_handler(this, |input_handler| {
1100 input_handler
1101 .replace_text_in_range(insert.replacement_range, &insert.text)
1102 });
1103 } else if !is_composing && is_held {
1104 if let Some(last_insert_text) = ime_text {
1105 //MacOS IME is a bit funky, and even when you've told it there's nothing to
1106 //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
1107 //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
1108 with_input_handler(this, |input_handler| {
1109 if input_handler.selected_text_range().is_none() {
1110 handled = true;
1111 input_handler.replace_text_in_range(None, &last_insert_text)
1112 }
1113 });
1114 }
1115 }
1116 }
1117
1118 window_state.lock().event_callback = Some(callback);
1119 }
1120 } else {
1121 handled = true;
1122 }
1123
1124 handled as BOOL
1125 } else {
1126 NO
1127 }
1128}
1129
1130extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
1131 let window_state = unsafe { get_window_state(this) };
1132 let weak_window_state = Arc::downgrade(&window_state);
1133 let mut lock = window_state.as_ref().lock();
1134 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1135
1136 let window_height = lock.content_size().height;
1137 let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) };
1138
1139 if let Some(mut event) = event {
1140 let synthesized_second_event = match &mut event {
1141 InputEvent::MouseDown(
1142 event @ MouseDownEvent {
1143 button: MouseButton::Left,
1144 modifiers: Modifiers { control: true, .. },
1145 ..
1146 },
1147 ) => {
1148 *event = MouseDownEvent {
1149 button: MouseButton::Right,
1150 modifiers: Modifiers {
1151 control: false,
1152 ..event.modifiers
1153 },
1154 click_count: 1,
1155 ..*event
1156 };
1157
1158 Some(InputEvent::MouseDown(MouseDownEvent {
1159 button: MouseButton::Right,
1160 ..*event
1161 }))
1162 }
1163
1164 // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
1165 // the ctrl-left_up to avoid having a mismatch in button down/up events if the
1166 // user is still holding ctrl when releasing the left mouse button
1167 InputEvent::MouseUp(MouseUpEvent {
1168 button: MouseButton::Left,
1169 modifiers: Modifiers { control: true, .. },
1170 ..
1171 }) => {
1172 lock.synthetic_drag_counter += 1;
1173 return;
1174 }
1175
1176 _ => None,
1177 };
1178
1179 match &event {
1180 InputEvent::MouseMoved(
1181 event @ MouseMoveEvent {
1182 pressed_button: Some(_),
1183 ..
1184 },
1185 ) => {
1186 lock.synthetic_drag_counter += 1;
1187 let executor = lock.executor.clone();
1188 executor
1189 .spawn_on_main_local(synthetic_drag(
1190 weak_window_state,
1191 lock.synthetic_drag_counter,
1192 event.clone(),
1193 ))
1194 .detach();
1195 }
1196
1197 InputEvent::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
1198
1199 InputEvent::MouseUp(MouseUpEvent {
1200 button: MouseButton::Left,
1201 ..
1202 }) => {
1203 lock.synthetic_drag_counter += 1;
1204 }
1205
1206 InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
1207 // Only raise modifiers changed event when they have actually changed
1208 if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent {
1209 modifiers: prev_modifiers,
1210 })) = &lock.previous_modifiers_changed_event
1211 {
1212 if prev_modifiers == modifiers {
1213 return;
1214 }
1215 }
1216
1217 lock.previous_modifiers_changed_event = Some(event.clone());
1218 }
1219
1220 _ => {}
1221 }
1222
1223 if let Some(mut callback) = lock.event_callback.take() {
1224 drop(lock);
1225 callback(event);
1226 if let Some(event) = synthesized_second_event {
1227 callback(event);
1228 }
1229 window_state.lock().event_callback = Some(callback);
1230 }
1231 }
1232}
1233
1234// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
1235// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
1236extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
1237 let window_state = unsafe { get_window_state(this) };
1238 let mut lock = window_state.as_ref().lock();
1239
1240 let keystroke = Keystroke {
1241 modifiers: Default::default(),
1242 key: ".".into(),
1243 ime_key: None,
1244 };
1245 let event = InputEvent::KeyDown(KeyDownEvent {
1246 keystroke: keystroke.clone(),
1247 is_held: false,
1248 });
1249
1250 lock.last_fresh_keydown = Some(keystroke);
1251 if let Some(mut callback) = lock.event_callback.take() {
1252 drop(lock);
1253 callback(event);
1254 window_state.lock().event_callback = Some(callback);
1255 }
1256}
1257
1258extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
1259 let window_state = unsafe { get_window_state(this) };
1260 window_state.as_ref().lock().move_traffic_light();
1261}
1262
1263extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
1264 window_fullscreen_changed(this, true);
1265}
1266
1267extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
1268 window_fullscreen_changed(this, false);
1269}
1270
1271fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
1272 let window_state = unsafe { get_window_state(this) };
1273 let mut lock = window_state.as_ref().lock();
1274 if let Some(mut callback) = lock.fullscreen_callback.take() {
1275 drop(lock);
1276 callback(is_fullscreen);
1277 window_state.lock().fullscreen_callback = Some(callback);
1278 }
1279}
1280
1281extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
1282 let window_state = unsafe { get_window_state(this) };
1283 let mut lock = window_state.as_ref().lock();
1284 if let Some(mut callback) = lock.moved_callback.take() {
1285 drop(lock);
1286 callback();
1287 window_state.lock().moved_callback = Some(callback);
1288 }
1289}
1290
1291extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
1292 let window_state = unsafe { get_window_state(this) };
1293 let lock = window_state.lock();
1294 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1295
1296 // When opening a pop-up while the application isn't active, Cocoa sends a spurious
1297 // `windowDidBecomeKey` message to the previous key window even though that window
1298 // isn't actually key. This causes a bug if the application is later activated while
1299 // the pop-up is still open, making it impossible to activate the previous key window
1300 // even if the pop-up gets closed. The only way to activate it again is to de-activate
1301 // the app and re-activate it, which is a pretty bad UX.
1302 // The following code detects the spurious event and invokes `resignKeyWindow`:
1303 // in theory, we're not supposed to invoke this method manually but it balances out
1304 // the spurious `becomeKeyWindow` event and helps us work around that bug.
1305 if selector == sel!(windowDidBecomeKey:) {
1306 if !is_active {
1307 unsafe {
1308 let _: () = msg_send![lock.native_window, resignKeyWindow];
1309 return;
1310 }
1311 }
1312 }
1313
1314 let executor = lock.executor.clone();
1315 drop(lock);
1316 executor
1317 .spawn_on_main_local(async move {
1318 let mut lock = window_state.as_ref().lock();
1319 if let Some(mut callback) = lock.activate_callback.take() {
1320 drop(lock);
1321 callback(is_active);
1322 window_state.lock().activate_callback = Some(callback);
1323 };
1324 })
1325 .detach();
1326}
1327
1328extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
1329 let window_state = unsafe { get_window_state(this) };
1330 let mut lock = window_state.as_ref().lock();
1331 if let Some(mut callback) = lock.should_close_callback.take() {
1332 drop(lock);
1333 let should_close = callback();
1334 window_state.lock().should_close_callback = Some(callback);
1335 should_close as BOOL
1336 } else {
1337 YES
1338 }
1339}
1340
1341extern "C" fn close_window(this: &Object, _: Sel) {
1342 unsafe {
1343 let close_callback = {
1344 let window_state = get_window_state(this);
1345 window_state
1346 .as_ref()
1347 .try_lock()
1348 .and_then(|mut window_state| window_state.close_callback.take())
1349 };
1350
1351 if let Some(callback) = close_callback {
1352 callback();
1353 }
1354
1355 let _: () = msg_send![super(this, class!(NSWindow)), close];
1356 }
1357}
1358
1359extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
1360 let window_state = unsafe { get_window_state(this) };
1361 let window_state = window_state.as_ref().lock();
1362 window_state.renderer.layer().as_ptr() as id
1363}
1364
1365extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
1366 let window_state = unsafe { get_window_state(this) };
1367 let mut lock = window_state.as_ref().lock();
1368
1369 unsafe {
1370 let scale_factor = lock.scale_factor() as f64;
1371 let size = lock.content_size();
1372 let drawable_size: NSSize = NSSize {
1373 width: f64::from(size.width) * scale_factor,
1374 height: f64::from(size.height) * scale_factor,
1375 };
1376
1377 let _: () = msg_send![
1378 lock.renderer.layer(),
1379 setContentsScale: scale_factor
1380 ];
1381 let _: () = msg_send![
1382 lock.renderer.layer(),
1383 setDrawableSize: drawable_size
1384 ];
1385 }
1386
1387 if let Some(mut callback) = lock.resize_callback.take() {
1388 let content_size = lock.content_size();
1389 let scale_factor = lock.scale_factor();
1390 drop(lock);
1391 callback(content_size, scale_factor);
1392 window_state.as_ref().lock().resize_callback = Some(callback);
1393 };
1394}
1395
1396extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
1397 let window_state = unsafe { get_window_state(this) };
1398 let lock = window_state.as_ref().lock();
1399
1400 if lock.content_size() == size.into() {
1401 return;
1402 }
1403
1404 unsafe {
1405 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
1406 }
1407
1408 let scale_factor = lock.scale_factor() as f64;
1409 let drawable_size: NSSize = NSSize {
1410 width: size.width * scale_factor,
1411 height: size.height * scale_factor,
1412 };
1413
1414 unsafe {
1415 let _: () = msg_send![
1416 lock.renderer.layer(),
1417 setDrawableSize: drawable_size
1418 ];
1419 }
1420
1421 drop(lock);
1422 let mut lock = window_state.lock();
1423 if let Some(mut callback) = lock.resize_callback.take() {
1424 let content_size = lock.content_size();
1425 let scale_factor = lock.scale_factor();
1426 drop(lock);
1427 callback(content_size, scale_factor);
1428 window_state.lock().resize_callback = Some(callback);
1429 };
1430}
1431
1432extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1433 unsafe {
1434 let window_state = get_window_state(this);
1435 let mut window_state = window_state.as_ref().lock();
1436 if let Some(scene) = window_state.scene_to_render.take() {
1437 window_state.renderer.draw(&scene);
1438 }
1439 }
1440}
1441
1442extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1443 unsafe { msg_send![class!(NSArray), array] }
1444}
1445
1446extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1447 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1448 .flatten()
1449 .is_some() as BOOL
1450}
1451
1452extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1453 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1454 .flatten()
1455 .map_or(NSRange::invalid(), |range| range.into())
1456}
1457
1458extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1459 with_input_handler(this, |input_handler| input_handler.selected_text_range())
1460 .flatten()
1461 .map_or(NSRange::invalid(), |range| range.into())
1462}
1463
1464extern "C" fn first_rect_for_character_range(
1465 this: &Object,
1466 _: Sel,
1467 range: NSRange,
1468 _: id,
1469) -> NSRect {
1470 let frame = unsafe {
1471 let window = get_window_state(this).lock().native_window;
1472 NSView::frame(window)
1473 };
1474 with_input_handler(this, |input_handler| {
1475 input_handler.bounds_for_range(range.to_range()?)
1476 })
1477 .flatten()
1478 .map_or(
1479 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1480 |bounds| {
1481 NSRect::new(
1482 NSPoint::new(
1483 frame.origin.x + bounds.origin.x as f64,
1484 frame.origin.y + frame.size.height - bounds.origin.y as f64,
1485 ),
1486 NSSize::new(bounds.size.width as f64, bounds.size.height as f64),
1487 )
1488 },
1489 )
1490}
1491
1492extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1493 unsafe {
1494 let window_state = get_window_state(this);
1495 let mut lock = window_state.lock();
1496 let pending_key_down = lock.pending_key_down.take();
1497 drop(lock);
1498
1499 let is_attributed_string: BOOL =
1500 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1501 let text: id = if is_attributed_string == YES {
1502 msg_send![text, string]
1503 } else {
1504 text
1505 };
1506 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1507 .to_str()
1508 .unwrap();
1509 let replacement_range = replacement_range.to_range();
1510
1511 window_state.lock().ime_text = Some(text.to_string());
1512 window_state.lock().ime_state = ImeState::Acted;
1513
1514 let is_composing =
1515 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1516 .flatten()
1517 .is_some();
1518
1519 if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
1520 with_input_handler(this, |input_handler| {
1521 input_handler.replace_text_in_range(replacement_range, text)
1522 });
1523 } else {
1524 let mut pending_key_down = pending_key_down.unwrap();
1525 pending_key_down.1 = Some(InsertText {
1526 replacement_range,
1527 text: text.to_string(),
1528 });
1529 window_state.lock().pending_key_down = Some(pending_key_down);
1530 }
1531 }
1532}
1533
1534extern "C" fn set_marked_text(
1535 this: &Object,
1536 _: Sel,
1537 text: id,
1538 selected_range: NSRange,
1539 replacement_range: NSRange,
1540) {
1541 unsafe {
1542 let window_state = get_window_state(this);
1543 window_state.lock().pending_key_down.take();
1544
1545 let is_attributed_string: BOOL =
1546 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1547 let text: id = if is_attributed_string == YES {
1548 msg_send![text, string]
1549 } else {
1550 text
1551 };
1552 let selected_range = selected_range.to_range();
1553 let replacement_range = replacement_range.to_range();
1554 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1555 .to_str()
1556 .unwrap();
1557
1558 window_state.lock().ime_state = ImeState::Acted;
1559 window_state.lock().ime_text = Some(text.to_string());
1560
1561 with_input_handler(this, |input_handler| {
1562 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
1563 });
1564 }
1565}
1566
1567extern "C" fn unmark_text(this: &Object, _: Sel) {
1568 unsafe {
1569 let state = get_window_state(this);
1570 let mut borrow = state.lock();
1571 borrow.ime_state = ImeState::Acted;
1572 borrow.ime_text.take();
1573 }
1574
1575 with_input_handler(this, |input_handler| input_handler.unmark_text());
1576}
1577
1578extern "C" fn attributed_substring_for_proposed_range(
1579 this: &Object,
1580 _: Sel,
1581 range: NSRange,
1582 _actual_range: *mut c_void,
1583) -> id {
1584 with_input_handler(this, |input_handler| {
1585 let range = range.to_range()?;
1586 if range.is_empty() {
1587 return None;
1588 }
1589
1590 let selected_text = input_handler.text_for_range(range)?;
1591 unsafe {
1592 let string: id = msg_send![class!(NSAttributedString), alloc];
1593 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
1594 Some(string)
1595 }
1596 })
1597 .flatten()
1598 .unwrap_or(nil)
1599}
1600
1601extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
1602 unsafe {
1603 let state = get_window_state(this);
1604 let mut borrow = state.lock();
1605 borrow.ime_state = ImeState::Continue;
1606 borrow.ime_text.take();
1607 }
1608}
1609
1610extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
1611 unsafe {
1612 let state = get_window_state(this);
1613 let mut lock = state.as_ref().lock();
1614 if let Some(mut callback) = lock.appearance_changed_callback.take() {
1615 drop(lock);
1616 callback();
1617 state.lock().appearance_changed_callback = Some(callback);
1618 }
1619 }
1620}
1621
1622extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
1623 unsafe {
1624 let state = get_window_state(this);
1625 let lock = state.as_ref().lock();
1626 return if lock.kind == WindowKind::PopUp {
1627 YES
1628 } else {
1629 NO
1630 };
1631 }
1632}
1633
1634extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
1635 let window_state = unsafe { get_window_state(this) };
1636 let position = drag_event_position(&window_state, dragging_info);
1637 if send_new_event(
1638 &window_state,
1639 InputEvent::FileDrop(FileDropEvent::Pending { position }),
1640 ) {
1641 NSDragOperationCopy
1642 } else {
1643 NSDragOperationNone
1644 }
1645}
1646
1647extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDragOperation {
1648 let window_state = unsafe { get_window_state(this) };
1649 let position = drag_event_position(&window_state, dragging_info);
1650 if send_new_event(
1651 &window_state,
1652 InputEvent::FileDrop(FileDropEvent::Pending { position }),
1653 ) {
1654 NSDragOperationCopy
1655 } else {
1656 NSDragOperationNone
1657 }
1658}
1659
1660extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) {
1661 let window_state = unsafe { get_window_state(this) };
1662 send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::End));
1663}
1664
1665extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) -> BOOL {
1666 let mut paths = Vec::new();
1667 let pb: id = unsafe { msg_send![dragging_info, draggingPasteboard] };
1668 let filenames = unsafe { NSPasteboard::propertyListForType(pb, NSFilenamesPboardType) };
1669 for file in unsafe { filenames.iter() } {
1670 let path = unsafe {
1671 let f = NSString::UTF8String(file);
1672 CStr::from_ptr(f).to_string_lossy().into_owned()
1673 };
1674 paths.push(PathBuf::from(path))
1675 }
1676
1677 let window_state = unsafe { get_window_state(this) };
1678 let position = drag_event_position(&window_state, dragging_info);
1679 if send_new_event(
1680 &window_state,
1681 InputEvent::FileDrop(FileDropEvent::Submit { position, paths }),
1682 ) {
1683 YES
1684 } else {
1685 NO
1686 }
1687}
1688
1689extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) {
1690 let window_state = unsafe { get_window_state(this) };
1691 send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::End));
1692}
1693
1694async fn synthetic_drag(
1695 window_state: Weak<Mutex<MacWindowState>>,
1696 drag_id: usize,
1697 event: MouseMoveEvent,
1698) {
1699 loop {
1700 Timer::after(Duration::from_millis(16)).await;
1701 if let Some(window_state) = window_state.upgrade() {
1702 let mut lock = window_state.lock();
1703 if lock.synthetic_drag_counter == drag_id {
1704 if let Some(mut callback) = lock.event_callback.take() {
1705 drop(lock);
1706 callback(InputEvent::MouseMoved(event.clone()));
1707 window_state.lock().event_callback = Some(callback);
1708 }
1709 } else {
1710 break;
1711 }
1712 }
1713 }
1714}
1715
1716fn send_new_event(window_state_lock: &Mutex<MacWindowState>, e: InputEvent) -> bool {
1717 let window_state = window_state_lock.lock().event_callback.take();
1718 if let Some(mut callback) = window_state {
1719 callback(e);
1720 window_state_lock.lock().event_callback = Some(callback);
1721 true
1722 } else {
1723 false
1724 }
1725}
1726
1727fn drag_event_position(window_state: &Mutex<MacWindowState>, dragging_info: id) -> Point<Pixels> {
1728 let drag_location: NSPoint = unsafe { msg_send![dragging_info, draggingLocation] };
1729 convert_mouse_position(drag_location, window_state.lock().content_size().height)
1730}
1731
1732fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
1733where
1734 F: FnOnce(&mut dyn PlatformInputHandler) -> R,
1735{
1736 let window_state = unsafe { get_window_state(window) };
1737 let mut lock = window_state.as_ref().lock();
1738 if let Some(mut input_handler) = lock.input_handler.take() {
1739 drop(lock);
1740 let result = f(input_handler.as_mut());
1741 window_state.lock().input_handler = Some(input_handler);
1742 Some(result)
1743 } else {
1744 None
1745 }
1746}