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