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