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