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