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