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