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