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