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