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