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