1use crate::{
2 executor,
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 keymap::Keystroke,
8 mac::platform::{NSKeyValueObservingOptionNew, NSViewLayerContentsRedrawDuringViewResize},
9 platform::{
10 self,
11 mac::{geometry::RectFExt, renderer::Renderer},
12 Event, WindowBounds,
13 },
14 InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
15 MouseMovedEvent, Scene,
16};
17use block::ConcreteBlock;
18use cocoa::{
19 appkit::{
20 CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
21 NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
22 },
23 base::{id, nil},
24 foundation::{
25 NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger,
26 },
27};
28use core_graphics::display::CGRect;
29use ctor::ctor;
30use foreign_types::ForeignTypeRef;
31use objc::{
32 class,
33 declare::ClassDecl,
34 msg_send,
35 runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
36 sel, sel_impl,
37};
38use postage::oneshot;
39use smol::Timer;
40use std::{
41 any::Any,
42 cell::{Cell, RefCell},
43 convert::TryInto,
44 ffi::{c_void, CStr},
45 mem,
46 ops::Range,
47 os::raw::c_char,
48 ptr,
49 rc::{Rc, Weak},
50 sync::Arc,
51 time::Duration,
52};
53
54const WINDOW_STATE_IVAR: &str = "windowState";
55
56static mut WINDOW_CLASS: *const Class = ptr::null();
57static mut VIEW_CLASS: *const Class = ptr::null();
58
59#[repr(C)]
60#[derive(Copy, Clone, Debug)]
61struct NSRange {
62 pub location: NSUInteger,
63 pub length: NSUInteger,
64}
65
66impl NSRange {
67 fn invalid() -> Self {
68 Self {
69 location: NSNotFound as NSUInteger,
70 length: 0,
71 }
72 }
73
74 fn is_valid(&self) -> bool {
75 self.location != NSNotFound as NSUInteger
76 }
77
78 fn to_range(self) -> Option<Range<usize>> {
79 if self.is_valid() {
80 let start = self.location as usize;
81 let end = start + self.length as usize;
82 Some(start..end)
83 } else {
84 None
85 }
86 }
87}
88
89impl From<Range<usize>> for NSRange {
90 fn from(range: Range<usize>) -> Self {
91 NSRange {
92 location: range.start as NSUInteger,
93 length: range.len() as NSUInteger,
94 }
95 }
96}
97
98unsafe impl objc::Encode for NSRange {
99 fn encode() -> objc::Encoding {
100 let encoding = format!(
101 "{{NSRange={}{}}}",
102 NSUInteger::encode().as_str(),
103 NSUInteger::encode().as_str()
104 );
105 unsafe { objc::Encoding::from_str(&encoding) }
106 }
107}
108
109#[ctor]
110unsafe fn build_classes() {
111 WINDOW_CLASS = {
112 let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
113 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
114 decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
115 decl.add_method(
116 sel!(canBecomeMainWindow),
117 yes as extern "C" fn(&Object, Sel) -> BOOL,
118 );
119 decl.add_method(
120 sel!(canBecomeKeyWindow),
121 yes as extern "C" fn(&Object, Sel) -> BOOL,
122 );
123 decl.add_method(
124 sel!(sendEvent:),
125 send_event as extern "C" fn(&Object, Sel, id),
126 );
127 decl.add_method(
128 sel!(windowDidResize:),
129 window_did_resize as extern "C" fn(&Object, Sel, id),
130 );
131 decl.add_method(
132 sel!(windowWillEnterFullScreen:),
133 window_will_enter_fullscreen as extern "C" fn(&Object, Sel, id),
134 );
135 decl.add_method(
136 sel!(windowWillExitFullScreen:),
137 window_will_exit_fullscreen as extern "C" fn(&Object, Sel, id),
138 );
139 decl.add_method(
140 sel!(windowDidBecomeKey:),
141 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
142 );
143 decl.add_method(
144 sel!(windowDidResignKey:),
145 window_did_change_key_status as extern "C" fn(&Object, Sel, id),
146 );
147 decl.add_method(
148 sel!(windowShouldClose:),
149 window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
150 );
151 decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
152 decl.register()
153 };
154
155 VIEW_CLASS = {
156 let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
157 decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
158
159 decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
160
161 decl.add_method(
162 sel!(performKeyEquivalent:),
163 handle_key_equivalent as extern "C" fn(&Object, Sel, id) -> BOOL,
164 );
165 decl.add_method(
166 sel!(keyDown:),
167 handle_key_down as extern "C" fn(&Object, Sel, id),
168 );
169 decl.add_method(
170 sel!(mouseDown:),
171 handle_view_event as extern "C" fn(&Object, Sel, id),
172 );
173 decl.add_method(
174 sel!(mouseUp:),
175 handle_view_event as extern "C" fn(&Object, Sel, id),
176 );
177 decl.add_method(
178 sel!(rightMouseDown:),
179 handle_view_event as extern "C" fn(&Object, Sel, id),
180 );
181 decl.add_method(
182 sel!(rightMouseUp:),
183 handle_view_event as extern "C" fn(&Object, Sel, id),
184 );
185 decl.add_method(
186 sel!(otherMouseDown:),
187 handle_view_event as extern "C" fn(&Object, Sel, id),
188 );
189 decl.add_method(
190 sel!(otherMouseUp:),
191 handle_view_event as extern "C" fn(&Object, Sel, id),
192 );
193 decl.add_method(
194 sel!(mouseMoved:),
195 handle_view_event as extern "C" fn(&Object, Sel, id),
196 );
197 decl.add_method(
198 sel!(mouseDragged:),
199 handle_view_event as extern "C" fn(&Object, Sel, id),
200 );
201 decl.add_method(
202 sel!(scrollWheel:),
203 handle_view_event as extern "C" fn(&Object, Sel, id),
204 );
205 decl.add_method(
206 sel!(flagsChanged:),
207 handle_view_event as extern "C" fn(&Object, Sel, id),
208 );
209 decl.add_method(
210 sel!(cancelOperation:),
211 cancel_operation as extern "C" fn(&Object, Sel, id),
212 );
213
214 decl.add_method(
215 sel!(makeBackingLayer),
216 make_backing_layer as extern "C" fn(&Object, Sel) -> id,
217 );
218
219 decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
220 decl.add_method(
221 sel!(viewDidChangeBackingProperties),
222 view_did_change_backing_properties as extern "C" fn(&Object, Sel),
223 );
224 decl.add_method(
225 sel!(setFrameSize:),
226 set_frame_size as extern "C" fn(&Object, Sel, NSSize),
227 );
228 decl.add_method(
229 sel!(displayLayer:),
230 display_layer as extern "C" fn(&Object, Sel, id),
231 );
232
233 decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
234 decl.add_method(
235 sel!(validAttributesForMarkedText),
236 valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
237 );
238 decl.add_method(
239 sel!(hasMarkedText),
240 has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
241 );
242 decl.add_method(
243 sel!(markedRange),
244 marked_range as extern "C" fn(&Object, Sel) -> NSRange,
245 );
246 decl.add_method(
247 sel!(selectedRange),
248 selected_range as extern "C" fn(&Object, Sel) -> NSRange,
249 );
250 decl.add_method(
251 sel!(firstRectForCharacterRange:actualRange:),
252 first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
253 );
254 decl.add_method(
255 sel!(insertText:replacementRange:),
256 insert_text as extern "C" fn(&Object, Sel, id, NSRange),
257 );
258 decl.add_method(
259 sel!(setMarkedText:selectedRange:replacementRange:),
260 set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
261 );
262 decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
263 decl.add_method(
264 sel!(attributedSubstringForProposedRange:actualRange:),
265 attributed_substring_for_proposed_range
266 as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
267 );
268 decl.add_method(
269 sel!(observeValueForKeyPath:ofObject:change:context:),
270 appearance_changed as extern "C" fn(&Object, Sel, id, id, id, id),
271 );
272
273 // Suppress beep on keystrokes with modifier keys.
274 decl.add_method(
275 sel!(doCommandBySelector:),
276 do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
277 );
278
279 decl.register()
280 };
281}
282
283pub struct Window(Rc<RefCell<WindowState>>);
284
285///Used to track what the IME does when we send it a keystroke.
286///This is only used to handle the case where the IME mysteriously
287///swallows certain keys.
288///
289///Basically a direct copy of the approach that WezTerm uses in:
290///github.com/wez/wezterm : d5755f3e : window/src/os/macos/window.rs
291enum ImeState {
292 Continue,
293 Acted,
294 None,
295}
296
297struct WindowState {
298 id: usize,
299 native_window: id,
300 event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
301 activate_callback: Option<Box<dyn FnMut(bool)>>,
302 resize_callback: Option<Box<dyn FnMut()>>,
303 fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
304 should_close_callback: Option<Box<dyn FnMut() -> bool>>,
305 close_callback: Option<Box<dyn FnOnce()>>,
306 appearance_changed_callback: Option<Box<dyn FnMut()>>,
307 input_handler: Option<Box<dyn InputHandler>>,
308 pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
309 performed_key_equivalent: bool,
310 synthetic_drag_counter: usize,
311 executor: Rc<executor::Foreground>,
312 scene_to_render: Option<Scene>,
313 renderer: Renderer,
314 last_fresh_keydown: Option<Keystroke>,
315 traffic_light_position: Option<Vector2F>,
316 previous_modifiers_changed_event: Option<Event>,
317 //State tracking what the IME did after the last request
318 ime_state: ImeState,
319 //Retains the last IME Text
320 ime_text: Option<String>,
321}
322
323struct InsertText {
324 replacement_range: Option<Range<usize>>,
325 text: String,
326}
327
328impl Window {
329 pub fn open(
330 id: usize,
331 options: platform::WindowOptions,
332 executor: Rc<executor::Foreground>,
333 fonts: Arc<dyn platform::FontSystem>,
334 ) -> Self {
335 unsafe {
336 let pool = NSAutoreleasePool::new(nil);
337
338 let frame = match options.bounds {
339 WindowBounds::Maximized => RectF::new(Default::default(), vec2f(1024., 768.)),
340 WindowBounds::Fixed(rect) => rect,
341 }
342 .to_ns_rect();
343
344 let mut style_mask;
345 if let Some(titlebar) = options.titlebar.as_ref() {
346 style_mask = NSWindowStyleMask::NSClosableWindowMask
347 | NSWindowStyleMask::NSMiniaturizableWindowMask
348 | NSWindowStyleMask::NSResizableWindowMask
349 | NSWindowStyleMask::NSTitledWindowMask;
350
351 if titlebar.appears_transparent {
352 style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
353 }
354 } else {
355 style_mask = NSWindowStyleMask::empty();
356 }
357
358 let native_window: id = msg_send![WINDOW_CLASS, alloc];
359 let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
360 frame,
361 style_mask,
362 NSBackingStoreBuffered,
363 NO,
364 );
365 assert!(!native_window.is_null());
366
367 if matches!(options.bounds, WindowBounds::Maximized) {
368 let screen = native_window.screen();
369 native_window.setFrame_display_(screen.visibleFrame(), YES);
370 }
371
372 let native_view: id = msg_send![VIEW_CLASS, alloc];
373 let native_view = NSView::init(native_view);
374 assert!(!native_view.is_null());
375
376 let window = Self(Rc::new(RefCell::new(WindowState {
377 id,
378 native_window,
379 event_callback: None,
380 resize_callback: None,
381 should_close_callback: None,
382 close_callback: None,
383 activate_callback: None,
384 fullscreen_callback: None,
385 appearance_changed_callback: None,
386 input_handler: None,
387 pending_key_down: None,
388 performed_key_equivalent: false,
389 synthetic_drag_counter: 0,
390 executor,
391 scene_to_render: Default::default(),
392 renderer: Renderer::new(true, fonts),
393 last_fresh_keydown: None,
394 traffic_light_position: options
395 .titlebar
396 .as_ref()
397 .and_then(|titlebar| titlebar.traffic_light_position),
398 previous_modifiers_changed_event: None,
399 ime_state: ImeState::None,
400 ime_text: None,
401 })));
402
403 (*native_window).set_ivar(
404 WINDOW_STATE_IVAR,
405 Rc::into_raw(window.0.clone()) as *const c_void,
406 );
407 native_window.setDelegate_(native_window);
408 (*native_view).set_ivar(
409 WINDOW_STATE_IVAR,
410 Rc::into_raw(window.0.clone()) as *const c_void,
411 );
412
413 if let Some(titlebar) = options.titlebar {
414 if let Some(title) = titlebar.title {
415 native_window.setTitle_(NSString::alloc(nil).init_str(title));
416 }
417 if titlebar.appears_transparent {
418 native_window.setTitlebarAppearsTransparent_(YES);
419 }
420 }
421
422 native_window.setAcceptsMouseMovedEvents_(YES);
423
424 native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
425 native_view.setWantsBestResolutionOpenGLSurface_(YES);
426
427 // From winit crate: On Mojave, views automatically become layer-backed shortly after
428 // being added to a native_window. Changing the layer-backedness of a view breaks the
429 // association between the view and its associated OpenGL context. To work around this,
430 // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
431 // itself and break the association with its context.
432 native_view.setWantsLayer(YES);
433 let _: () = msg_send![
434 native_view,
435 setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
436 ];
437
438 native_window.setContentView_(native_view.autorelease());
439 native_window.makeFirstResponder_(native_view);
440
441 native_window.center();
442 native_window.makeKeyAndOrderFront_(nil);
443 let _: () = msg_send![
444 native_window,
445 addObserver: native_view
446 forKeyPath: NSString::alloc(nil).init_str("effectiveAppearance")
447 options: NSKeyValueObservingOptionNew
448 context: nil
449 ];
450
451 window.0.borrow().move_traffic_light();
452 pool.drain();
453
454 window
455 }
456 }
457
458 pub fn key_window_id() -> Option<usize> {
459 unsafe {
460 let app = NSApplication::sharedApplication(nil);
461 let key_window: id = msg_send![app, keyWindow];
462 if msg_send![key_window, isKindOfClass: WINDOW_CLASS] {
463 let id = get_window_state(&*key_window).borrow().id;
464 Some(id)
465 } else {
466 None
467 }
468 }
469 }
470}
471
472impl Drop for Window {
473 fn drop(&mut self) {
474 let this = self.0.borrow();
475 let window = this.native_window;
476 this.executor
477 .spawn(async move {
478 unsafe {
479 window.close();
480 }
481 })
482 .detach();
483 }
484}
485
486impl platform::Window for Window {
487 fn as_any_mut(&mut self) -> &mut dyn Any {
488 self
489 }
490
491 fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>) {
492 self.0.as_ref().borrow_mut().event_callback = Some(callback);
493 }
494
495 fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
496 self.0.as_ref().borrow_mut().resize_callback = Some(callback);
497 }
498
499 fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
500 self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback);
501 }
502
503 fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
504 self.0.as_ref().borrow_mut().should_close_callback = Some(callback);
505 }
506
507 fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
508 self.0.as_ref().borrow_mut().close_callback = Some(callback);
509 }
510
511 fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
512 self.0.as_ref().borrow_mut().activate_callback = Some(callback);
513 }
514
515 fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
516 self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
517 }
518
519 fn prompt(
520 &self,
521 level: platform::PromptLevel,
522 msg: &str,
523 answers: &[&str],
524 ) -> oneshot::Receiver<usize> {
525 unsafe {
526 let alert: id = msg_send![class!(NSAlert), alloc];
527 let alert: id = msg_send![alert, init];
528 let alert_style = match level {
529 platform::PromptLevel::Info => 1,
530 platform::PromptLevel::Warning => 0,
531 platform::PromptLevel::Critical => 2,
532 };
533 let _: () = msg_send![alert, setAlertStyle: alert_style];
534 let _: () = msg_send![alert, setMessageText: ns_string(msg)];
535 for (ix, answer) in answers.iter().enumerate() {
536 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
537 let _: () = msg_send![button, setTag: ix as NSInteger];
538 }
539 let (done_tx, done_rx) = oneshot::channel();
540 let done_tx = Cell::new(Some(done_tx));
541 let block = ConcreteBlock::new(move |answer: NSInteger| {
542 if let Some(mut done_tx) = done_tx.take() {
543 let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap());
544 }
545 });
546 let block = block.copy();
547 let native_window = self.0.borrow().native_window;
548 self.0
549 .borrow()
550 .executor
551 .spawn(async move {
552 let _: () = msg_send![
553 alert,
554 beginSheetModalForWindow: native_window
555 completionHandler: block
556 ];
557 })
558 .detach();
559
560 done_rx
561 }
562 }
563
564 fn activate(&self) {
565 let window = self.0.borrow().native_window;
566 self.0
567 .borrow()
568 .executor
569 .spawn(async move {
570 unsafe {
571 let _: () = msg_send![window, makeKeyAndOrderFront: nil];
572 }
573 })
574 .detach();
575 }
576
577 fn set_title(&mut self, title: &str) {
578 unsafe {
579 let app = NSApplication::sharedApplication(nil);
580 let window = self.0.borrow().native_window;
581 let title = ns_string(title);
582 msg_send![app, changeWindowsItem:window title:title filename:false]
583 }
584 }
585
586 fn set_edited(&mut self, edited: bool) {
587 unsafe {
588 let window = self.0.borrow().native_window;
589 msg_send![window, setDocumentEdited: edited as BOOL]
590 }
591
592 // Changing the document edited state resets the traffic light position,
593 // so we have to move it again.
594 self.0.borrow().move_traffic_light();
595 }
596
597 fn show_character_palette(&self) {
598 unsafe {
599 let app = NSApplication::sharedApplication(nil);
600 let window = self.0.borrow().native_window;
601 let _: () = msg_send![app, orderFrontCharacterPalette: window];
602 }
603 }
604
605 fn minimize(&self) {
606 let window = self.0.borrow().native_window;
607 unsafe {
608 window.miniaturize_(nil);
609 }
610 }
611
612 fn zoom(&self) {
613 let this = self.0.borrow();
614 let window = this.native_window;
615 this.executor
616 .spawn(async move {
617 unsafe {
618 window.zoom_(nil);
619 }
620 })
621 .detach();
622 }
623
624 fn toggle_full_screen(&self) {
625 let this = self.0.borrow();
626 let window = this.native_window;
627 this.executor
628 .spawn(async move {
629 unsafe {
630 window.toggleFullScreen_(nil);
631 }
632 })
633 .detach();
634 }
635
636 fn size(&self) -> Vector2F {
637 self.0.as_ref().borrow().size()
638 }
639
640 fn scale_factor(&self) -> f32 {
641 self.0.as_ref().borrow().scale_factor()
642 }
643
644 fn present_scene(&mut self, scene: Scene) {
645 self.0.as_ref().borrow_mut().present_scene(scene);
646 }
647
648 fn titlebar_height(&self) -> f32 {
649 self.0.as_ref().borrow().titlebar_height()
650 }
651
652 fn appearance(&self) -> crate::Appearance {
653 unsafe {
654 let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
655 crate::Appearance::from_native(appearance)
656 }
657 }
658
659 fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
660 self.0.borrow_mut().appearance_changed_callback = Some(callback);
661 }
662}
663
664impl WindowState {
665 fn move_traffic_light(&self) {
666 if let Some(traffic_light_position) = self.traffic_light_position {
667 let titlebar_height = self.titlebar_height();
668
669 unsafe {
670 let close_button: id = msg_send![
671 self.native_window,
672 standardWindowButton: NSWindowButton::NSWindowCloseButton
673 ];
674 let min_button: id = msg_send![
675 self.native_window,
676 standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
677 ];
678 let zoom_button: id = msg_send![
679 self.native_window,
680 standardWindowButton: NSWindowButton::NSWindowZoomButton
681 ];
682
683 let mut close_button_frame: CGRect = msg_send![close_button, frame];
684 let mut min_button_frame: CGRect = msg_send![min_button, frame];
685 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
686 let mut origin = vec2f(
687 traffic_light_position.x(),
688 titlebar_height
689 - traffic_light_position.y()
690 - close_button_frame.size.height as f32,
691 );
692 let button_spacing =
693 (min_button_frame.origin.x - close_button_frame.origin.x) as f32;
694
695 close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
696 let _: () = msg_send![close_button, setFrame: close_button_frame];
697 origin.set_x(origin.x() + button_spacing);
698
699 min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
700 let _: () = msg_send![min_button, setFrame: min_button_frame];
701 origin.set_x(origin.x() + button_spacing);
702
703 zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
704 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
705 }
706 }
707 }
708
709 fn size(&self) -> Vector2F {
710 let NSSize { width, height, .. } =
711 unsafe { NSView::frame(self.native_window.contentView()) }.size;
712 vec2f(width as f32, height as f32)
713 }
714
715 fn scale_factor(&self) -> f32 {
716 get_scale_factor(self.native_window)
717 }
718
719 fn titlebar_height(&self) -> f32 {
720 unsafe {
721 let frame = NSWindow::frame(self.native_window);
722 let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
723 (frame.size.height - content_layout_rect.size.height) as f32
724 }
725 }
726
727 fn present_scene(&mut self, scene: Scene) {
728 self.scene_to_render = Some(scene);
729 unsafe {
730 let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
731 }
732 }
733}
734
735fn get_scale_factor(native_window: id) -> f32 {
736 unsafe {
737 let screen: id = msg_send![native_window, screen];
738 NSScreen::backingScaleFactor(screen) as f32
739 }
740}
741
742unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
743 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
744 let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
745 let rc2 = rc1.clone();
746 mem::forget(rc1);
747 rc2
748}
749
750unsafe fn drop_window_state(object: &Object) {
751 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
752 Rc::from_raw(raw as *mut RefCell<WindowState>);
753}
754
755extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
756 YES
757}
758
759extern "C" fn dealloc_window(this: &Object, _: Sel) {
760 unsafe {
761 drop_window_state(this);
762 let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
763 }
764}
765
766extern "C" fn dealloc_view(this: &Object, _: Sel) {
767 unsafe {
768 drop_window_state(this);
769 let _: () = msg_send![super(this, class!(NSView)), dealloc];
770 }
771}
772
773extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
774 handle_key_event(this, native_event, true)
775}
776
777extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
778 handle_key_event(this, native_event, false);
779}
780
781extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
782 let window_state = unsafe { get_window_state(this) };
783
784 let mut window_state_borrow = window_state.as_ref().borrow_mut();
785
786 let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
787
788 if let Some(event) = event {
789 if key_equivalent {
790 window_state_borrow.performed_key_equivalent = true;
791 } else if window_state_borrow.performed_key_equivalent {
792 return NO;
793 }
794
795 let function_is_held;
796 window_state_borrow.pending_key_down = match event {
797 Event::KeyDown(event) => {
798 let keydown = event.keystroke.clone();
799 // Ignore events from held-down keys after some of the initially-pressed keys
800 // were released.
801 if event.is_held {
802 if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
803 return YES;
804 }
805 } else {
806 window_state_borrow.last_fresh_keydown = Some(keydown);
807 }
808 function_is_held = event.keystroke.function;
809 Some((event, None))
810 }
811 _ => return NO,
812 };
813
814 drop(window_state_borrow);
815
816 if !function_is_held {
817 unsafe {
818 let input_context: id = msg_send![this, inputContext];
819 let _: BOOL = msg_send![input_context, handleEvent: native_event];
820 }
821 }
822
823 let mut handled = false;
824 let mut window_state_borrow = window_state.borrow_mut();
825 let ime_text = window_state_borrow.ime_text.clone();
826 if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
827 let is_held = event.is_held;
828 if let Some(mut callback) = window_state_borrow.event_callback.take() {
829 drop(window_state_borrow);
830
831 let is_composing =
832 with_input_handler(this, |input_handler| input_handler.marked_text_range())
833 .flatten()
834 .is_some();
835 if !is_composing {
836 handled = callback(Event::KeyDown(event));
837 }
838
839 if !handled {
840 if let Some(insert) = insert_text {
841 handled = true;
842 with_input_handler(this, |input_handler| {
843 input_handler
844 .replace_text_in_range(insert.replacement_range, &insert.text)
845 });
846 } else if !is_composing && is_held {
847 if let Some(last_insert_text) = ime_text {
848 //MacOS IME is a bit funky, and even when you've told it there's nothing to
849 //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
850 //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
851 with_input_handler(this, |input_handler| {
852 if input_handler.selected_text_range().is_none() {
853 handled = true;
854 input_handler.replace_text_in_range(None, &last_insert_text)
855 }
856 });
857 }
858 }
859 }
860
861 window_state.borrow_mut().event_callback = Some(callback);
862 }
863 } else {
864 handled = true;
865 }
866
867 handled as BOOL
868 } else {
869 NO
870 }
871}
872
873extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
874 let window_state = unsafe { get_window_state(this) };
875 let weak_window_state = Rc::downgrade(&window_state);
876 let mut window_state_borrow = window_state.as_ref().borrow_mut();
877
878 let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
879 if let Some(event) = event {
880 match &event {
881 Event::MouseMoved(
882 event @ MouseMovedEvent {
883 pressed_button: Some(_),
884 ..
885 },
886 ) => {
887 window_state_borrow.synthetic_drag_counter += 1;
888 window_state_borrow
889 .executor
890 .spawn(synthetic_drag(
891 weak_window_state,
892 window_state_borrow.synthetic_drag_counter,
893 *event,
894 ))
895 .detach();
896 }
897 Event::MouseUp(MouseButtonEvent {
898 button: MouseButton::Left,
899 ..
900 }) => {
901 window_state_borrow.synthetic_drag_counter += 1;
902 }
903 Event::ModifiersChanged(ModifiersChangedEvent {
904 ctrl,
905 alt,
906 shift,
907 cmd,
908 }) => {
909 // Only raise modifiers changed event when they have actually changed
910 if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
911 ctrl: prev_ctrl,
912 alt: prev_alt,
913 shift: prev_shift,
914 cmd: prev_cmd,
915 })) = &window_state_borrow.previous_modifiers_changed_event
916 {
917 if prev_ctrl == ctrl
918 && prev_alt == alt
919 && prev_shift == shift
920 && prev_cmd == cmd
921 {
922 return;
923 }
924 }
925
926 window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
927 }
928 _ => {}
929 }
930
931 if let Some(mut callback) = window_state_borrow.event_callback.take() {
932 drop(window_state_borrow);
933 callback(event);
934 window_state.borrow_mut().event_callback = Some(callback);
935 }
936 }
937}
938
939// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
940// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
941extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
942 let window_state = unsafe { get_window_state(this) };
943 let mut window_state_borrow = window_state.as_ref().borrow_mut();
944
945 let keystroke = Keystroke {
946 cmd: true,
947 ctrl: false,
948 alt: false,
949 shift: false,
950 function: false,
951 key: ".".into(),
952 };
953 let event = Event::KeyDown(KeyDownEvent {
954 keystroke: keystroke.clone(),
955 is_held: false,
956 });
957
958 window_state_borrow.last_fresh_keydown = Some(keystroke);
959 if let Some(mut callback) = window_state_borrow.event_callback.take() {
960 drop(window_state_borrow);
961 callback(event);
962 window_state.borrow_mut().event_callback = Some(callback);
963 }
964}
965
966extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
967 unsafe {
968 let _: () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
969 get_window_state(this).borrow_mut().performed_key_equivalent = false;
970 }
971}
972
973extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
974 let window_state = unsafe { get_window_state(this) };
975 window_state.as_ref().borrow().move_traffic_light();
976}
977
978extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
979 window_fullscreen_changed(this, true);
980}
981
982extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
983 window_fullscreen_changed(this, false);
984}
985
986fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
987 let window_state = unsafe { get_window_state(this) };
988 let mut window_state_borrow = window_state.as_ref().borrow_mut();
989 if let Some(mut callback) = window_state_borrow.fullscreen_callback.take() {
990 drop(window_state_borrow);
991 callback(is_fullscreen);
992 window_state.borrow_mut().fullscreen_callback = Some(callback);
993 }
994}
995
996extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
997 let is_active = if selector == sel!(windowDidBecomeKey:) {
998 true
999 } else if selector == sel!(windowDidResignKey:) {
1000 false
1001 } else {
1002 unreachable!();
1003 };
1004
1005 let window_state = unsafe { get_window_state(this) };
1006 let executor = window_state.as_ref().borrow().executor.clone();
1007 executor
1008 .spawn(async move {
1009 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1010 if let Some(mut callback) = window_state_borrow.activate_callback.take() {
1011 drop(window_state_borrow);
1012 callback(is_active);
1013 window_state.borrow_mut().activate_callback = Some(callback);
1014 };
1015 })
1016 .detach();
1017}
1018
1019extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
1020 let window_state = unsafe { get_window_state(this) };
1021 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1022 if let Some(mut callback) = window_state_borrow.should_close_callback.take() {
1023 drop(window_state_borrow);
1024 let should_close = callback();
1025 window_state.borrow_mut().should_close_callback = Some(callback);
1026 should_close as BOOL
1027 } else {
1028 YES
1029 }
1030}
1031
1032extern "C" fn close_window(this: &Object, _: Sel) {
1033 unsafe {
1034 let close_callback = {
1035 let window_state = get_window_state(this);
1036 window_state
1037 .as_ref()
1038 .try_borrow_mut()
1039 .ok()
1040 .and_then(|mut window_state| window_state.close_callback.take())
1041 };
1042
1043 if let Some(callback) = close_callback {
1044 callback();
1045 }
1046
1047 let _: () = msg_send![super(this, class!(NSWindow)), close];
1048 }
1049}
1050
1051extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
1052 let window_state = unsafe { get_window_state(this) };
1053 let window_state = window_state.as_ref().borrow();
1054 window_state.renderer.layer().as_ptr() as id
1055}
1056
1057extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
1058 let window_state = unsafe { get_window_state(this) };
1059 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1060
1061 unsafe {
1062 let scale_factor = window_state_borrow.scale_factor() as f64;
1063 let size = window_state_borrow.size();
1064 let drawable_size: NSSize = NSSize {
1065 width: size.x() as f64 * scale_factor,
1066 height: size.y() as f64 * scale_factor,
1067 };
1068
1069 let _: () = msg_send![
1070 window_state_borrow.renderer.layer(),
1071 setContentsScale: scale_factor
1072 ];
1073 let _: () = msg_send![
1074 window_state_borrow.renderer.layer(),
1075 setDrawableSize: drawable_size
1076 ];
1077 }
1078
1079 if let Some(mut callback) = window_state_borrow.resize_callback.take() {
1080 drop(window_state_borrow);
1081 callback();
1082 window_state.as_ref().borrow_mut().resize_callback = Some(callback);
1083 };
1084}
1085
1086extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
1087 let window_state = unsafe { get_window_state(this) };
1088 let window_state_borrow = window_state.as_ref().borrow();
1089
1090 if window_state_borrow.size() == vec2f(size.width as f32, size.height as f32) {
1091 return;
1092 }
1093
1094 unsafe {
1095 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
1096 }
1097
1098 let scale_factor = window_state_borrow.scale_factor() as f64;
1099 let drawable_size: NSSize = NSSize {
1100 width: size.width * scale_factor,
1101 height: size.height * scale_factor,
1102 };
1103
1104 unsafe {
1105 let _: () = msg_send![
1106 window_state_borrow.renderer.layer(),
1107 setDrawableSize: drawable_size
1108 ];
1109 }
1110
1111 drop(window_state_borrow);
1112 let mut window_state_borrow = window_state.borrow_mut();
1113 if let Some(mut callback) = window_state_borrow.resize_callback.take() {
1114 drop(window_state_borrow);
1115 callback();
1116 window_state.borrow_mut().resize_callback = Some(callback);
1117 };
1118}
1119
1120extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1121 unsafe {
1122 let window_state = get_window_state(this);
1123 let mut window_state = window_state.as_ref().borrow_mut();
1124 if let Some(scene) = window_state.scene_to_render.take() {
1125 window_state.renderer.render(&scene);
1126 };
1127 }
1128}
1129
1130extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1131 unsafe { msg_send![class!(NSArray), array] }
1132}
1133
1134extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1135 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1136 .flatten()
1137 .is_some() as BOOL
1138}
1139
1140extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1141 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1142 .flatten()
1143 .map_or(NSRange::invalid(), |range| range.into())
1144}
1145
1146extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1147 with_input_handler(this, |input_handler| input_handler.selected_text_range())
1148 .flatten()
1149 .map_or(NSRange::invalid(), |range| range.into())
1150}
1151
1152extern "C" fn first_rect_for_character_range(
1153 this: &Object,
1154 _: Sel,
1155 range: NSRange,
1156 _: id,
1157) -> NSRect {
1158 let frame = unsafe {
1159 let window = get_window_state(this).borrow().native_window;
1160 NSView::frame(window)
1161 };
1162 with_input_handler(this, |input_handler| {
1163 input_handler.rect_for_range(range.to_range()?)
1164 })
1165 .flatten()
1166 .map_or(
1167 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1168 |rect| {
1169 NSRect::new(
1170 NSPoint::new(
1171 frame.origin.x + rect.origin_x() as f64,
1172 frame.origin.y + frame.size.height - rect.origin_y() as f64,
1173 ),
1174 NSSize::new(rect.width() as f64, rect.height() as f64),
1175 )
1176 },
1177 )
1178}
1179
1180extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1181 unsafe {
1182 let window_state = get_window_state(this);
1183 let mut window_state_borrow = window_state.borrow_mut();
1184 let pending_key_down = window_state_borrow.pending_key_down.take();
1185 drop(window_state_borrow);
1186
1187 let is_attributed_string: BOOL =
1188 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1189 let text: id = if is_attributed_string == YES {
1190 msg_send![text, string]
1191 } else {
1192 text
1193 };
1194 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1195 .to_str()
1196 .unwrap();
1197 let replacement_range = replacement_range.to_range();
1198
1199 window_state.borrow_mut().ime_text = Some(text.to_string());
1200 window_state.borrow_mut().ime_state = ImeState::Acted;
1201
1202 let is_composing =
1203 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1204 .flatten()
1205 .is_some();
1206
1207 if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
1208 with_input_handler(this, |input_handler| {
1209 input_handler.replace_text_in_range(replacement_range, text)
1210 });
1211 } else {
1212 let mut pending_key_down = pending_key_down.unwrap();
1213 pending_key_down.1 = Some(InsertText {
1214 replacement_range,
1215 text: text.to_string(),
1216 });
1217 window_state.borrow_mut().pending_key_down = Some(pending_key_down);
1218 }
1219 }
1220}
1221
1222extern "C" fn set_marked_text(
1223 this: &Object,
1224 _: Sel,
1225 text: id,
1226 selected_range: NSRange,
1227 replacement_range: NSRange,
1228) {
1229 unsafe {
1230 let window_state = get_window_state(this);
1231 window_state.borrow_mut().pending_key_down.take();
1232
1233 let is_attributed_string: BOOL =
1234 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1235 let text: id = if is_attributed_string == YES {
1236 msg_send![text, string]
1237 } else {
1238 text
1239 };
1240 let selected_range = selected_range.to_range();
1241 let replacement_range = replacement_range.to_range();
1242 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1243 .to_str()
1244 .unwrap();
1245
1246 window_state.borrow_mut().ime_state = ImeState::Acted;
1247 window_state.borrow_mut().ime_text = Some(text.to_string());
1248
1249 with_input_handler(this, |input_handler| {
1250 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
1251 });
1252 }
1253}
1254
1255extern "C" fn unmark_text(this: &Object, _: Sel) {
1256 unsafe {
1257 let state = get_window_state(this);
1258 let mut borrow = state.borrow_mut();
1259 borrow.ime_state = ImeState::Acted;
1260 borrow.ime_text.take();
1261 }
1262
1263 with_input_handler(this, |input_handler| input_handler.unmark_text());
1264}
1265
1266extern "C" fn attributed_substring_for_proposed_range(
1267 this: &Object,
1268 _: Sel,
1269 range: NSRange,
1270 _actual_range: *mut c_void,
1271) -> id {
1272 with_input_handler(this, |input_handler| {
1273 let range = range.to_range()?;
1274 if range.is_empty() {
1275 return None;
1276 }
1277
1278 let selected_text = input_handler.text_for_range(range)?;
1279 unsafe {
1280 let string: id = msg_send![class!(NSAttributedString), alloc];
1281 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
1282 Some(string)
1283 }
1284 })
1285 .flatten()
1286 .unwrap_or(nil)
1287}
1288
1289extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
1290 unsafe {
1291 let state = get_window_state(this);
1292 let mut borrow = state.borrow_mut();
1293 borrow.ime_state = ImeState::Continue;
1294 borrow.ime_text.take();
1295 }
1296}
1297
1298extern "C" fn appearance_changed(this: &Object, _: Sel, _: id, _: id, _: id, _: id) {
1299 unsafe {
1300 let state = get_window_state(this);
1301 let mut state_borrow = state.as_ref().borrow_mut();
1302 if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
1303 drop(state_borrow);
1304 callback();
1305 state.borrow_mut().appearance_changed_callback = Some(callback);
1306 }
1307 }
1308}
1309
1310async fn synthetic_drag(
1311 window_state: Weak<RefCell<WindowState>>,
1312 drag_id: usize,
1313 event: MouseMovedEvent,
1314) {
1315 loop {
1316 Timer::after(Duration::from_millis(16)).await;
1317 if let Some(window_state) = window_state.upgrade() {
1318 let mut window_state_borrow = window_state.borrow_mut();
1319 if window_state_borrow.synthetic_drag_counter == drag_id {
1320 if let Some(mut callback) = window_state_borrow.event_callback.take() {
1321 drop(window_state_borrow);
1322 callback(Event::MouseMoved(event));
1323 window_state.borrow_mut().event_callback = Some(callback);
1324 }
1325 } else {
1326 break;
1327 }
1328 }
1329 }
1330}
1331
1332unsafe fn ns_string(string: &str) -> id {
1333 NSString::alloc(nil).init_str(string).autorelease()
1334}
1335
1336fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
1337where
1338 F: FnOnce(&mut dyn InputHandler) -> R,
1339{
1340 let window_state = unsafe { get_window_state(window) };
1341 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1342 if let Some(mut input_handler) = window_state_borrow.input_handler.take() {
1343 drop(window_state_borrow);
1344 let result = f(input_handler.as_mut());
1345 window_state.borrow_mut().input_handler = Some(input_handler);
1346 Some(result)
1347 } else {
1348 None
1349 }
1350}