1use super::{ns_string, MetalRenderer, NSRange};
2use crate::{
3 point, px, size, AnyWindowHandle, Bounds, Event, InputHandler, KeyDownEvent, Keystroke,
4 MacScreen, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMovedEvent,
5 MouseUpEvent, NSRectExt, Pixels, Platform, PlatformDispatcher, PlatformScreen, PlatformWindow,
6 Point, Scene, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions,
7 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 InputHandler>>,
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 InputHandler>) {
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
890fn get_scale_factor(native_window: id) -> f32 {
891 unsafe {
892 let screen: id = msg_send![native_window, screen];
893 NSScreen::backingScaleFactor(screen) as f32
894 }
895}
896
897unsafe fn get_window_state(object: &Object) -> Arc<Mutex<MacWindowState>> {
898 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
899 let rc1 = Arc::from_raw(raw as *mut Mutex<MacWindowState>);
900 let rc2 = rc1.clone();
901 mem::forget(rc1);
902 rc2
903}
904
905unsafe fn drop_window_state(object: &Object) {
906 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
907 Rc::from_raw(raw as *mut RefCell<MacWindowState>);
908}
909
910extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
911 YES
912}
913
914extern "C" fn dealloc_window(this: &Object, _: Sel) {
915 unsafe {
916 drop_window_state(this);
917 let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
918 }
919}
920
921extern "C" fn dealloc_view(this: &Object, _: Sel) {
922 unsafe {
923 drop_window_state(this);
924 let _: () = msg_send![super(this, class!(NSView)), dealloc];
925 }
926}
927
928extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
929 handle_key_event(this, native_event, true)
930}
931
932extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
933 handle_key_event(this, native_event, false);
934}
935
936extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
937 let window_state = unsafe { get_window_state(this) };
938 let mut lock = window_state.as_ref().lock();
939
940 let window_height = lock.content_size().height;
941 let event = unsafe { Event::from_native(native_event, Some(window_height)) };
942
943 if let Some(Event::KeyDown(event)) = event {
944 // For certain keystrokes, macOS will first dispatch a "key equivalent" event.
945 // If that event isn't handled, it will then dispatch a "key down" event. GPUI
946 // makes no distinction between these two types of events, so we need to ignore
947 // the "key down" event if we've already just processed its "key equivalent" version.
948 if key_equivalent {
949 lock.last_key_equivalent = Some(event.clone());
950 } else if lock.last_key_equivalent.take().as_ref() == Some(&event) {
951 return NO;
952 }
953
954 let keydown = event.keystroke.clone();
955 let fn_modifier = keydown.modifiers.function;
956 // Ignore events from held-down keys after some of the initially-pressed keys
957 // were released.
958 if event.is_held {
959 if lock.last_fresh_keydown.as_ref() != Some(&keydown) {
960 return YES;
961 }
962 } else {
963 lock.last_fresh_keydown = Some(keydown);
964 }
965 lock.pending_key_down = Some((event, None));
966 drop(lock);
967
968 // Send the event to the input context for IME handling, unless the `fn` modifier is
969 // being pressed.
970 if !fn_modifier {
971 unsafe {
972 let input_context: id = msg_send![this, inputContext];
973 let _: BOOL = msg_send![input_context, handleEvent: native_event];
974 }
975 }
976
977 let mut handled = false;
978 let mut lock = window_state.lock();
979 let ime_text = lock.ime_text.clone();
980 if let Some((event, insert_text)) = lock.pending_key_down.take() {
981 let is_held = event.is_held;
982 if let Some(mut callback) = lock.event_callback.take() {
983 drop(lock);
984
985 let is_composing =
986 with_input_handler(this, |input_handler| input_handler.marked_text_range())
987 .flatten()
988 .is_some();
989 if !is_composing {
990 // if the IME has changed the key, we'll first emit an event with the character
991 // generated by the IME system; then fallback to the keystroke if that is not
992 // handled.
993 // cases that we have working:
994 // - " on a brazillian layout by typing <quote><space>
995 // - ctrl-` on a brazillian layout by typing <ctrl-`>
996 // - $ on a czech QWERTY layout by typing <alt-4>
997 // - 4 on a czech QWERTY layout by typing <shift-4>
998 // - ctrl-4 on a czech QWERTY layout by typing <ctrl-alt-4> (or <ctrl-shift-4>)
999 if ime_text.is_some() && ime_text.as_ref() != Some(&event.keystroke.key) {
1000 let event_with_ime_text = KeyDownEvent {
1001 is_held: false,
1002 keystroke: Keystroke {
1003 // we match ctrl because some use-cases need it.
1004 // we don't match alt because it's often used to generate the optional character
1005 // we don't match shift because we're not here with letters (usually)
1006 // we don't match cmd/fn because they don't seem to use IME
1007 modifiers: Default::default(),
1008 key: ime_text.clone().unwrap(),
1009 },
1010 };
1011 handled = callback(Event::KeyDown(event_with_ime_text));
1012 }
1013 if !handled {
1014 // empty key happens when you type a deadkey in input composition.
1015 // (e.g. on a brazillian keyboard typing quote is a deadkey)
1016 if !event.keystroke.key.is_empty() {
1017 handled = callback(Event::KeyDown(event));
1018 }
1019 }
1020 }
1021
1022 if !handled {
1023 if let Some(insert) = insert_text {
1024 handled = true;
1025 with_input_handler(this, |input_handler| {
1026 input_handler
1027 .replace_text_in_range(insert.replacement_range, &insert.text)
1028 });
1029 } else if !is_composing && is_held {
1030 if let Some(last_insert_text) = ime_text {
1031 //MacOS IME is a bit funky, and even when you've told it there's nothing to
1032 //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
1033 //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
1034 with_input_handler(this, |input_handler| {
1035 if input_handler.selected_text_range().is_none() {
1036 handled = true;
1037 input_handler.replace_text_in_range(None, &last_insert_text)
1038 }
1039 });
1040 }
1041 }
1042 }
1043
1044 window_state.lock().event_callback = Some(callback);
1045 }
1046 } else {
1047 handled = true;
1048 }
1049
1050 handled as BOOL
1051 } else {
1052 NO
1053 }
1054}
1055
1056extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
1057 let window_state = unsafe { get_window_state(this) };
1058 let weak_window_state = Arc::downgrade(&window_state);
1059 let mut lock = window_state.as_ref().lock();
1060 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1061
1062 let window_height = lock.content_size().height;
1063 let event = unsafe { Event::from_native(native_event, Some(window_height)) };
1064
1065 if let Some(mut event) = event {
1066 let synthesized_second_event = match &mut event {
1067 Event::MouseDown(
1068 event @ MouseDownEvent {
1069 button: MouseButton::Left,
1070 modifiers: Modifiers { control: true, .. },
1071 ..
1072 },
1073 ) => {
1074 *event = MouseDownEvent {
1075 button: MouseButton::Right,
1076 modifiers: Modifiers {
1077 control: false,
1078 ..event.modifiers
1079 },
1080 click_count: 1,
1081 ..*event
1082 };
1083
1084 Some(Event::MouseDown(MouseDownEvent {
1085 button: MouseButton::Right,
1086 ..*event
1087 }))
1088 }
1089
1090 // Because we map a ctrl-left_down to a right_down -> right_up let's ignore
1091 // the ctrl-left_up to avoid having a mismatch in button down/up events if the
1092 // user is still holding ctrl when releasing the left mouse button
1093 Event::MouseUp(MouseUpEvent {
1094 button: MouseButton::Left,
1095 modifiers: Modifiers { control: true, .. },
1096 ..
1097 }) => {
1098 lock.synthetic_drag_counter += 1;
1099 return;
1100 }
1101
1102 _ => None,
1103 };
1104
1105 match &event {
1106 Event::MouseMoved(
1107 event @ MouseMovedEvent {
1108 pressed_button: Some(_),
1109 ..
1110 },
1111 ) => {
1112 lock.synthetic_drag_counter += 1;
1113 let dispatcher = lock.dispatcher.clone();
1114 let _ = crate::spawn_on_main_local(
1115 dispatcher,
1116 synthetic_drag(
1117 weak_window_state,
1118 lock.synthetic_drag_counter,
1119 event.clone(),
1120 ),
1121 );
1122 }
1123
1124 Event::MouseMoved(_) if !(is_active || lock.kind == WindowKind::PopUp) => return,
1125
1126 Event::MouseUp(MouseUpEvent {
1127 button: MouseButton::Left,
1128 ..
1129 }) => {
1130 lock.synthetic_drag_counter += 1;
1131 }
1132
1133 Event::ModifiersChanged(ModifiersChangedEvent { modifiers }) => {
1134 // Only raise modifiers changed event when they have actually changed
1135 if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
1136 modifiers: prev_modifiers,
1137 })) = &lock.previous_modifiers_changed_event
1138 {
1139 if prev_modifiers == modifiers {
1140 return;
1141 }
1142 }
1143
1144 lock.previous_modifiers_changed_event = Some(event.clone());
1145 }
1146
1147 _ => {}
1148 }
1149
1150 if let Some(mut callback) = lock.event_callback.take() {
1151 drop(lock);
1152 callback(event);
1153 if let Some(event) = synthesized_second_event {
1154 callback(event);
1155 }
1156 window_state.lock().event_callback = Some(callback);
1157 }
1158 }
1159}
1160
1161// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
1162// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
1163extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
1164 let window_state = unsafe { get_window_state(this) };
1165 let mut lock = window_state.as_ref().lock();
1166
1167 let keystroke = Keystroke {
1168 modifiers: Default::default(),
1169 key: ".".into(),
1170 };
1171 let event = Event::KeyDown(KeyDownEvent {
1172 keystroke: keystroke.clone(),
1173 is_held: false,
1174 });
1175
1176 lock.last_fresh_keydown = Some(keystroke);
1177 if let Some(mut callback) = lock.event_callback.take() {
1178 drop(lock);
1179 callback(event);
1180 window_state.lock().event_callback = Some(callback);
1181 }
1182}
1183
1184extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
1185 let window_state = unsafe { get_window_state(this) };
1186 window_state.as_ref().lock().move_traffic_light();
1187}
1188
1189extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
1190 window_fullscreen_changed(this, true);
1191}
1192
1193extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
1194 window_fullscreen_changed(this, false);
1195}
1196
1197fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
1198 let window_state = unsafe { get_window_state(this) };
1199 let mut lock = window_state.as_ref().lock();
1200 if let Some(mut callback) = lock.fullscreen_callback.take() {
1201 drop(lock);
1202 callback(is_fullscreen);
1203 window_state.lock().fullscreen_callback = Some(callback);
1204 }
1205}
1206
1207extern "C" fn window_did_move(this: &Object, _: Sel, _: id) {
1208 let window_state = unsafe { get_window_state(this) };
1209 let mut lock = window_state.as_ref().lock();
1210 if let Some(mut callback) = lock.moved_callback.take() {
1211 drop(lock);
1212 callback();
1213 window_state.lock().moved_callback = Some(callback);
1214 }
1215}
1216
1217extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
1218 let window_state = unsafe { get_window_state(this) };
1219 let lock = window_state.lock();
1220 let is_active = unsafe { lock.native_window.isKeyWindow() == YES };
1221
1222 // When opening a pop-up while the application isn't active, Cocoa sends a spurious
1223 // `windowDidBecomeKey` message to the previous key window even though that window
1224 // isn't actually key. This causes a bug if the application is later activated while
1225 // the pop-up is still open, making it impossible to activate the previous key window
1226 // even if the pop-up gets closed. The only way to activate it again is to de-activate
1227 // the app and re-activate it, which is a pretty bad UX.
1228 // The following code detects the spurious event and invokes `resignKeyWindow`:
1229 // in theory, we're not supposed to invoke this method manually but it balances out
1230 // the spurious `becomeKeyWindow` event and helps us work around that bug.
1231 if selector == sel!(windowDidBecomeKey:) {
1232 if !is_active {
1233 unsafe {
1234 let _: () = msg_send![lock.native_window, resignKeyWindow];
1235 return;
1236 }
1237 }
1238 }
1239
1240 let dispatcher = lock.dispatcher.clone();
1241 drop(lock);
1242 let _ = crate::spawn_on_main_local(dispatcher, async move {
1243 let mut lock = window_state.as_ref().lock();
1244 if let Some(mut callback) = lock.activate_callback.take() {
1245 drop(lock);
1246 callback(is_active);
1247 window_state.lock().activate_callback = Some(callback);
1248 };
1249 });
1250}
1251
1252extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
1253 let window_state = unsafe { get_window_state(this) };
1254 let mut lock = window_state.as_ref().lock();
1255 if let Some(mut callback) = lock.should_close_callback.take() {
1256 drop(lock);
1257 let should_close = callback();
1258 window_state.lock().should_close_callback = Some(callback);
1259 should_close as BOOL
1260 } else {
1261 YES
1262 }
1263}
1264
1265extern "C" fn close_window(this: &Object, _: Sel) {
1266 unsafe {
1267 let close_callback = {
1268 let window_state = get_window_state(this);
1269 window_state
1270 .as_ref()
1271 .try_lock()
1272 .and_then(|mut window_state| window_state.close_callback.take())
1273 };
1274
1275 if let Some(callback) = close_callback {
1276 callback();
1277 }
1278
1279 let _: () = msg_send![super(this, class!(NSWindow)), close];
1280 }
1281}
1282
1283extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
1284 let window_state = unsafe { get_window_state(this) };
1285 let window_state = window_state.as_ref().lock();
1286 window_state.renderer.layer().as_ptr() as id
1287}
1288
1289extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
1290 let window_state = unsafe { get_window_state(this) };
1291 let mut lock = window_state.as_ref().lock();
1292
1293 unsafe {
1294 let scale_factor = lock.scale_factor() as f64;
1295 let size = lock.content_size();
1296 let drawable_size: NSSize = NSSize {
1297 width: f64::from(size.width) * scale_factor,
1298 height: f64::from(size.height) * scale_factor,
1299 };
1300
1301 let _: () = msg_send![
1302 lock.renderer.layer(),
1303 setContentsScale: scale_factor
1304 ];
1305 let _: () = msg_send![
1306 lock.renderer.layer(),
1307 setDrawableSize: drawable_size
1308 ];
1309 }
1310
1311 if let Some(mut callback) = lock.resize_callback.take() {
1312 let content_size = lock.content_size();
1313 let scale_factor = lock.scale_factor();
1314 drop(lock);
1315 callback(content_size, scale_factor);
1316 window_state.as_ref().lock().resize_callback = Some(callback);
1317 };
1318}
1319
1320extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
1321 let window_state = unsafe { get_window_state(this) };
1322 let lock = window_state.as_ref().lock();
1323
1324 if lock.content_size() == size.into() {
1325 return;
1326 }
1327
1328 unsafe {
1329 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
1330 }
1331
1332 let scale_factor = lock.scale_factor() as f64;
1333 let drawable_size: NSSize = NSSize {
1334 width: size.width * scale_factor,
1335 height: size.height * scale_factor,
1336 };
1337
1338 unsafe {
1339 let _: () = msg_send![
1340 lock.renderer.layer(),
1341 setDrawableSize: drawable_size
1342 ];
1343 }
1344
1345 drop(lock);
1346 let mut lock = window_state.lock();
1347 if let Some(mut callback) = lock.resize_callback.take() {
1348 let content_size = lock.content_size();
1349 let scale_factor = lock.scale_factor();
1350 drop(lock);
1351 callback(content_size, scale_factor);
1352 window_state.lock().resize_callback = Some(callback);
1353 };
1354}
1355
1356extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1357 unsafe {
1358 let window_state = get_window_state(this);
1359 let mut window_state = window_state.as_ref().lock();
1360 if let Some(scene) = window_state.scene_to_render.take() {
1361 dbg!("render", &scene);
1362 window_state.renderer.draw(&scene);
1363 }
1364 }
1365}
1366
1367extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1368 unsafe { msg_send![class!(NSArray), array] }
1369}
1370
1371extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1372 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1373 .flatten()
1374 .is_some() as BOOL
1375}
1376
1377extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1378 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1379 .flatten()
1380 .map_or(NSRange::invalid(), |range| range.into())
1381}
1382
1383extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1384 with_input_handler(this, |input_handler| input_handler.selected_text_range())
1385 .flatten()
1386 .map_or(NSRange::invalid(), |range| range.into())
1387}
1388
1389extern "C" fn first_rect_for_character_range(
1390 this: &Object,
1391 _: Sel,
1392 range: NSRange,
1393 _: id,
1394) -> NSRect {
1395 let frame = unsafe {
1396 let window = get_window_state(this).lock().native_window;
1397 NSView::frame(window)
1398 };
1399 with_input_handler(this, |input_handler| {
1400 input_handler.bounds_for_range(range.to_range()?)
1401 })
1402 .flatten()
1403 .map_or(
1404 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1405 |bounds| {
1406 NSRect::new(
1407 NSPoint::new(
1408 frame.origin.x + bounds.origin.x as f64,
1409 frame.origin.y + frame.size.height - bounds.origin.y as f64,
1410 ),
1411 NSSize::new(bounds.size.width as f64, bounds.size.height as f64),
1412 )
1413 },
1414 )
1415}
1416
1417extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1418 unsafe {
1419 let window_state = get_window_state(this);
1420 let mut lock = window_state.lock();
1421 let pending_key_down = lock.pending_key_down.take();
1422 drop(lock);
1423
1424 let is_attributed_string: BOOL =
1425 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1426 let text: id = if is_attributed_string == YES {
1427 msg_send![text, string]
1428 } else {
1429 text
1430 };
1431 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1432 .to_str()
1433 .unwrap();
1434 let replacement_range = replacement_range.to_range();
1435
1436 window_state.lock().ime_text = Some(text.to_string());
1437 window_state.lock().ime_state = ImeState::Acted;
1438
1439 let is_composing =
1440 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1441 .flatten()
1442 .is_some();
1443
1444 if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
1445 with_input_handler(this, |input_handler| {
1446 input_handler.replace_text_in_range(replacement_range, text)
1447 });
1448 } else {
1449 let mut pending_key_down = pending_key_down.unwrap();
1450 pending_key_down.1 = Some(InsertText {
1451 replacement_range,
1452 text: text.to_string(),
1453 });
1454 window_state.lock().pending_key_down = Some(pending_key_down);
1455 }
1456 }
1457}
1458
1459extern "C" fn set_marked_text(
1460 this: &Object,
1461 _: Sel,
1462 text: id,
1463 selected_range: NSRange,
1464 replacement_range: NSRange,
1465) {
1466 unsafe {
1467 let window_state = get_window_state(this);
1468 window_state.lock().pending_key_down.take();
1469
1470 let is_attributed_string: BOOL =
1471 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1472 let text: id = if is_attributed_string == YES {
1473 msg_send![text, string]
1474 } else {
1475 text
1476 };
1477 let selected_range = selected_range.to_range();
1478 let replacement_range = replacement_range.to_range();
1479 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1480 .to_str()
1481 .unwrap();
1482
1483 window_state.lock().ime_state = ImeState::Acted;
1484 window_state.lock().ime_text = Some(text.to_string());
1485
1486 with_input_handler(this, |input_handler| {
1487 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
1488 });
1489 }
1490}
1491
1492extern "C" fn unmark_text(this: &Object, _: Sel) {
1493 unsafe {
1494 let state = get_window_state(this);
1495 let mut borrow = state.lock();
1496 borrow.ime_state = ImeState::Acted;
1497 borrow.ime_text.take();
1498 }
1499
1500 with_input_handler(this, |input_handler| input_handler.unmark_text());
1501}
1502
1503extern "C" fn attributed_substring_for_proposed_range(
1504 this: &Object,
1505 _: Sel,
1506 range: NSRange,
1507 _actual_range: *mut c_void,
1508) -> id {
1509 with_input_handler(this, |input_handler| {
1510 let range = range.to_range()?;
1511 if range.is_empty() {
1512 return None;
1513 }
1514
1515 let selected_text = input_handler.text_for_range(range)?;
1516 unsafe {
1517 let string: id = msg_send![class!(NSAttributedString), alloc];
1518 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
1519 Some(string)
1520 }
1521 })
1522 .flatten()
1523 .unwrap_or(nil)
1524}
1525
1526extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
1527 unsafe {
1528 let state = get_window_state(this);
1529 let mut borrow = state.lock();
1530 borrow.ime_state = ImeState::Continue;
1531 borrow.ime_text.take();
1532 }
1533}
1534
1535extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
1536 unsafe {
1537 let state = get_window_state(this);
1538 let mut lock = state.as_ref().lock();
1539 if let Some(mut callback) = lock.appearance_changed_callback.take() {
1540 drop(lock);
1541 callback();
1542 state.lock().appearance_changed_callback = Some(callback);
1543 }
1544 }
1545}
1546
1547extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
1548 unsafe {
1549 let state = get_window_state(this);
1550 let lock = state.as_ref().lock();
1551 return if lock.kind == WindowKind::PopUp {
1552 YES
1553 } else {
1554 NO
1555 };
1556 }
1557}
1558
1559async fn synthetic_drag(
1560 window_state: Weak<Mutex<MacWindowState>>,
1561 drag_id: usize,
1562 event: MouseMovedEvent,
1563) {
1564 loop {
1565 Timer::after(Duration::from_millis(16)).await;
1566 if let Some(window_state) = window_state.upgrade() {
1567 let mut lock = window_state.lock();
1568 if lock.synthetic_drag_counter == drag_id {
1569 if let Some(mut callback) = lock.event_callback.take() {
1570 drop(lock);
1571 callback(Event::MouseMoved(event.clone()));
1572 window_state.lock().event_callback = Some(callback);
1573 }
1574 } else {
1575 break;
1576 }
1577 }
1578 }
1579}
1580
1581fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
1582where
1583 F: FnOnce(&mut dyn InputHandler) -> R,
1584{
1585 let window_state = unsafe { get_window_state(window) };
1586 let mut lock = window_state.as_ref().lock();
1587 if let Some(mut input_handler) = lock.input_handler.take() {
1588 drop(lock);
1589 let result = f(input_handler.as_mut());
1590 window_state.lock().input_handler = Some(input_handler);
1591 Some(result)
1592 } else {
1593 None
1594 }
1595}