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