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