1use crate::{
2 executor,
3 geometry::{
4 rect::RectF,
5 vector::{vec2f, Vector2F},
6 },
7 keymap::Keystroke,
8 mac::platform::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!(viewDidChangeEffectiveAppearance),
270 view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
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 mut style_mask;
339 if let Some(titlebar) = options.titlebar.as_ref() {
340 style_mask = NSWindowStyleMask::NSClosableWindowMask
341 | NSWindowStyleMask::NSMiniaturizableWindowMask
342 | NSWindowStyleMask::NSResizableWindowMask
343 | NSWindowStyleMask::NSTitledWindowMask;
344
345 if titlebar.appears_transparent {
346 style_mask |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
347 }
348 } else {
349 style_mask = NSWindowStyleMask::empty();
350 }
351
352 let native_window: id = msg_send![WINDOW_CLASS, alloc];
353 let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
354 RectF::new(Default::default(), vec2f(1024., 768.)).to_ns_rect(),
355 style_mask,
356 NSBackingStoreBuffered,
357 NO,
358 );
359 assert!(!native_window.is_null());
360
361 let screen = native_window.screen();
362 match options.bounds {
363 WindowBounds::Maximized => {
364 native_window.setFrame_display_(screen.visibleFrame(), YES);
365 }
366 WindowBounds::Fixed(top_left_bounds) => {
367 let frame = screen.visibleFrame();
368 let bottom_left_bounds = RectF::new(
369 vec2f(
370 top_left_bounds.origin_x(),
371 frame.size.height as f32
372 - top_left_bounds.origin_y()
373 - top_left_bounds.height(),
374 ),
375 top_left_bounds.size(),
376 );
377 native_window.setFrame_display_(bottom_left_bounds.to_ns_rect(), YES);
378 }
379 }
380
381 let native_view: id = msg_send![VIEW_CLASS, alloc];
382 let native_view = NSView::init(native_view);
383 assert!(!native_view.is_null());
384
385 let window = Self(Rc::new(RefCell::new(WindowState {
386 id,
387 native_window,
388 event_callback: None,
389 resize_callback: None,
390 should_close_callback: None,
391 close_callback: None,
392 activate_callback: None,
393 fullscreen_callback: None,
394 appearance_changed_callback: None,
395 input_handler: None,
396 pending_key_down: None,
397 performed_key_equivalent: false,
398 synthetic_drag_counter: 0,
399 executor,
400 scene_to_render: Default::default(),
401 renderer: Renderer::new(true, fonts),
402 last_fresh_keydown: None,
403 traffic_light_position: options
404 .titlebar
405 .as_ref()
406 .and_then(|titlebar| titlebar.traffic_light_position),
407 previous_modifiers_changed_event: None,
408 ime_state: ImeState::None,
409 ime_text: None,
410 })));
411
412 (*native_window).set_ivar(
413 WINDOW_STATE_IVAR,
414 Rc::into_raw(window.0.clone()) as *const c_void,
415 );
416 native_window.setDelegate_(native_window);
417 (*native_view).set_ivar(
418 WINDOW_STATE_IVAR,
419 Rc::into_raw(window.0.clone()) as *const c_void,
420 );
421
422 if let Some(titlebar) = options.titlebar {
423 if let Some(title) = titlebar.title {
424 native_window.setTitle_(NSString::alloc(nil).init_str(title));
425 }
426 if titlebar.appears_transparent {
427 native_window.setTitlebarAppearsTransparent_(YES);
428 }
429 }
430
431 native_window.setAcceptsMouseMovedEvents_(YES);
432
433 native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
434 native_view.setWantsBestResolutionOpenGLSurface_(YES);
435
436 // From winit crate: On Mojave, views automatically become layer-backed shortly after
437 // being added to a native_window. Changing the layer-backedness of a view breaks the
438 // association between the view and its associated OpenGL context. To work around this,
439 // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
440 // itself and break the association with its context.
441 native_view.setWantsLayer(YES);
442 let _: () = msg_send![
443 native_view,
444 setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
445 ];
446
447 native_window.setContentView_(native_view.autorelease());
448 native_window.makeFirstResponder_(native_view);
449
450 if options.center {
451 native_window.center();
452 }
453
454 native_window.makeKeyAndOrderFront_(nil);
455
456 window.0.borrow().move_traffic_light();
457 pool.drain();
458
459 window
460 }
461 }
462
463 pub fn key_window_id() -> Option<usize> {
464 unsafe {
465 let app = NSApplication::sharedApplication(nil);
466 let key_window: id = msg_send![app, keyWindow];
467 if msg_send![key_window, isKindOfClass: WINDOW_CLASS] {
468 let id = get_window_state(&*key_window).borrow().id;
469 Some(id)
470 } else {
471 None
472 }
473 }
474 }
475}
476
477impl Drop for Window {
478 fn drop(&mut self) {
479 let this = self.0.borrow();
480 let window = this.native_window;
481 this.executor
482 .spawn(async move {
483 unsafe {
484 window.close();
485 }
486 })
487 .detach();
488 }
489}
490
491impl platform::Window for Window {
492 fn as_any_mut(&mut self) -> &mut dyn Any {
493 self
494 }
495
496 fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>) {
497 self.0.as_ref().borrow_mut().event_callback = Some(callback);
498 }
499
500 fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
501 self.0.as_ref().borrow_mut().resize_callback = Some(callback);
502 }
503
504 fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
505 self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback);
506 }
507
508 fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
509 self.0.as_ref().borrow_mut().should_close_callback = Some(callback);
510 }
511
512 fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
513 self.0.as_ref().borrow_mut().close_callback = Some(callback);
514 }
515
516 fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>) {
517 self.0.as_ref().borrow_mut().activate_callback = Some(callback);
518 }
519
520 fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
521 self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
522 }
523
524 fn prompt(
525 &self,
526 level: platform::PromptLevel,
527 msg: &str,
528 answers: &[&str],
529 ) -> oneshot::Receiver<usize> {
530 unsafe {
531 let alert: id = msg_send![class!(NSAlert), alloc];
532 let alert: id = msg_send![alert, init];
533 let alert_style = match level {
534 platform::PromptLevel::Info => 1,
535 platform::PromptLevel::Warning => 0,
536 platform::PromptLevel::Critical => 2,
537 };
538 let _: () = msg_send![alert, setAlertStyle: alert_style];
539 let _: () = msg_send![alert, setMessageText: ns_string(msg)];
540 for (ix, answer) in answers.iter().enumerate() {
541 let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
542 let _: () = msg_send![button, setTag: ix as NSInteger];
543 }
544 let (done_tx, done_rx) = oneshot::channel();
545 let done_tx = Cell::new(Some(done_tx));
546 let block = ConcreteBlock::new(move |answer: NSInteger| {
547 if let Some(mut done_tx) = done_tx.take() {
548 let _ = postage::sink::Sink::try_send(&mut done_tx, answer.try_into().unwrap());
549 }
550 });
551 let block = block.copy();
552 let native_window = self.0.borrow().native_window;
553 self.0
554 .borrow()
555 .executor
556 .spawn(async move {
557 let _: () = msg_send![
558 alert,
559 beginSheetModalForWindow: native_window
560 completionHandler: block
561 ];
562 })
563 .detach();
564
565 done_rx
566 }
567 }
568
569 fn activate(&self) {
570 let window = self.0.borrow().native_window;
571 self.0
572 .borrow()
573 .executor
574 .spawn(async move {
575 unsafe {
576 let _: () = msg_send![window, makeKeyAndOrderFront: nil];
577 }
578 })
579 .detach();
580 }
581
582 fn set_title(&mut self, title: &str) {
583 unsafe {
584 let app = NSApplication::sharedApplication(nil);
585 let window = self.0.borrow().native_window;
586 let title = ns_string(title);
587 msg_send![app, changeWindowsItem:window title:title filename:false]
588 }
589 }
590
591 fn set_edited(&mut self, edited: bool) {
592 unsafe {
593 let window = self.0.borrow().native_window;
594 msg_send![window, setDocumentEdited: edited as BOOL]
595 }
596
597 // Changing the document edited state resets the traffic light position,
598 // so we have to move it again.
599 self.0.borrow().move_traffic_light();
600 }
601
602 fn show_character_palette(&self) {
603 unsafe {
604 let app = NSApplication::sharedApplication(nil);
605 let window = self.0.borrow().native_window;
606 let _: () = msg_send![app, orderFrontCharacterPalette: window];
607 }
608 }
609
610 fn minimize(&self) {
611 let window = self.0.borrow().native_window;
612 unsafe {
613 window.miniaturize_(nil);
614 }
615 }
616
617 fn zoom(&self) {
618 let this = self.0.borrow();
619 let window = this.native_window;
620 this.executor
621 .spawn(async move {
622 unsafe {
623 window.zoom_(nil);
624 }
625 })
626 .detach();
627 }
628
629 fn toggle_full_screen(&self) {
630 let this = self.0.borrow();
631 let window = this.native_window;
632 this.executor
633 .spawn(async move {
634 unsafe {
635 window.toggleFullScreen_(nil);
636 }
637 })
638 .detach();
639 }
640
641 fn bounds(&self) -> RectF {
642 self.0.as_ref().borrow().bounds()
643 }
644
645 fn content_size(&self) -> Vector2F {
646 self.0.as_ref().borrow().content_size()
647 }
648
649 fn scale_factor(&self) -> f32 {
650 self.0.as_ref().borrow().scale_factor()
651 }
652
653 fn present_scene(&mut self, scene: Scene) {
654 self.0.as_ref().borrow_mut().present_scene(scene);
655 }
656
657 fn titlebar_height(&self) -> f32 {
658 self.0.as_ref().borrow().titlebar_height()
659 }
660
661 fn appearance(&self) -> crate::Appearance {
662 unsafe {
663 let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
664 crate::Appearance::from_native(appearance)
665 }
666 }
667
668 fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
669 self.0.borrow_mut().appearance_changed_callback = Some(callback);
670 }
671}
672
673impl WindowState {
674 fn move_traffic_light(&self) {
675 if let Some(traffic_light_position) = self.traffic_light_position {
676 let titlebar_height = self.titlebar_height();
677
678 unsafe {
679 let close_button: id = msg_send![
680 self.native_window,
681 standardWindowButton: NSWindowButton::NSWindowCloseButton
682 ];
683 let min_button: id = msg_send![
684 self.native_window,
685 standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
686 ];
687 let zoom_button: id = msg_send![
688 self.native_window,
689 standardWindowButton: NSWindowButton::NSWindowZoomButton
690 ];
691
692 let mut close_button_frame: CGRect = msg_send![close_button, frame];
693 let mut min_button_frame: CGRect = msg_send![min_button, frame];
694 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
695 let mut origin = vec2f(
696 traffic_light_position.x(),
697 titlebar_height
698 - traffic_light_position.y()
699 - close_button_frame.size.height as f32,
700 );
701 let button_spacing =
702 (min_button_frame.origin.x - close_button_frame.origin.x) as f32;
703
704 close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
705 let _: () = msg_send![close_button, setFrame: close_button_frame];
706 origin.set_x(origin.x() + button_spacing);
707
708 min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
709 let _: () = msg_send![min_button, setFrame: min_button_frame];
710 origin.set_x(origin.x() + button_spacing);
711
712 zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
713 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
714 }
715 }
716 }
717
718 fn bounds(&self) -> RectF {
719 unsafe {
720 let screen_frame = self.native_window.screen().visibleFrame();
721 let window_frame = NSWindow::frame(self.native_window);
722 let origin = vec2f(
723 window_frame.origin.x as f32,
724 (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
725 as f32,
726 );
727 let size = vec2f(
728 window_frame.size.width as f32,
729 window_frame.size.height as f32,
730 );
731 RectF::new(origin, size)
732 }
733 }
734
735 fn content_size(&self) -> Vector2F {
736 let NSSize { width, height, .. } =
737 unsafe { NSView::frame(self.native_window.contentView()) }.size;
738 vec2f(width as f32, height as f32)
739 }
740
741 fn scale_factor(&self) -> f32 {
742 get_scale_factor(self.native_window)
743 }
744
745 fn titlebar_height(&self) -> f32 {
746 unsafe {
747 let frame = NSWindow::frame(self.native_window);
748 let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
749 (frame.size.height - content_layout_rect.size.height) as f32
750 }
751 }
752
753 fn present_scene(&mut self, scene: Scene) {
754 self.scene_to_render = Some(scene);
755 unsafe {
756 let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
757 }
758 }
759}
760
761fn get_scale_factor(native_window: id) -> f32 {
762 unsafe {
763 let screen: id = msg_send![native_window, screen];
764 NSScreen::backingScaleFactor(screen) as f32
765 }
766}
767
768unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
769 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
770 let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
771 let rc2 = rc1.clone();
772 mem::forget(rc1);
773 rc2
774}
775
776unsafe fn drop_window_state(object: &Object) {
777 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
778 Rc::from_raw(raw as *mut RefCell<WindowState>);
779}
780
781extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
782 YES
783}
784
785extern "C" fn dealloc_window(this: &Object, _: Sel) {
786 unsafe {
787 drop_window_state(this);
788 let _: () = msg_send![super(this, class!(NSWindow)), dealloc];
789 }
790}
791
792extern "C" fn dealloc_view(this: &Object, _: Sel) {
793 unsafe {
794 drop_window_state(this);
795 let _: () = msg_send![super(this, class!(NSView)), dealloc];
796 }
797}
798
799extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
800 handle_key_event(this, native_event, true)
801}
802
803extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
804 handle_key_event(this, native_event, false);
805}
806
807extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
808 let window_state = unsafe { get_window_state(this) };
809
810 let mut window_state_borrow = window_state.as_ref().borrow_mut();
811
812 let event =
813 unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) };
814
815 if let Some(event) = event {
816 if key_equivalent {
817 window_state_borrow.performed_key_equivalent = true;
818 } else if window_state_borrow.performed_key_equivalent {
819 return NO;
820 }
821
822 let function_is_held;
823 window_state_borrow.pending_key_down = match event {
824 Event::KeyDown(event) => {
825 let keydown = event.keystroke.clone();
826 // Ignore events from held-down keys after some of the initially-pressed keys
827 // were released.
828 if event.is_held {
829 if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
830 return YES;
831 }
832 } else {
833 window_state_borrow.last_fresh_keydown = Some(keydown);
834 }
835 function_is_held = event.keystroke.function;
836 Some((event, None))
837 }
838 _ => return NO,
839 };
840
841 drop(window_state_borrow);
842
843 if !function_is_held {
844 unsafe {
845 let input_context: id = msg_send![this, inputContext];
846 let _: BOOL = msg_send![input_context, handleEvent: native_event];
847 }
848 }
849
850 let mut handled = false;
851 let mut window_state_borrow = window_state.borrow_mut();
852 let ime_text = window_state_borrow.ime_text.clone();
853 if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
854 let is_held = event.is_held;
855 if let Some(mut callback) = window_state_borrow.event_callback.take() {
856 drop(window_state_borrow);
857
858 let is_composing =
859 with_input_handler(this, |input_handler| input_handler.marked_text_range())
860 .flatten()
861 .is_some();
862 if !is_composing {
863 handled = callback(Event::KeyDown(event));
864 }
865
866 if !handled {
867 if let Some(insert) = insert_text {
868 handled = true;
869 with_input_handler(this, |input_handler| {
870 input_handler
871 .replace_text_in_range(insert.replacement_range, &insert.text)
872 });
873 } else if !is_composing && is_held {
874 if let Some(last_insert_text) = ime_text {
875 //MacOS IME is a bit funky, and even when you've told it there's nothing to
876 //inter it will still swallow certain keys (e.g. 'f', 'j') and not others
877 //(e.g. 'n'). This is a problem for certain kinds of views, like the terminal
878 with_input_handler(this, |input_handler| {
879 if input_handler.selected_text_range().is_none() {
880 handled = true;
881 input_handler.replace_text_in_range(None, &last_insert_text)
882 }
883 });
884 }
885 }
886 }
887
888 window_state.borrow_mut().event_callback = Some(callback);
889 }
890 } else {
891 handled = true;
892 }
893
894 handled as BOOL
895 } else {
896 NO
897 }
898}
899
900extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
901 let window_state = unsafe { get_window_state(this) };
902 let weak_window_state = Rc::downgrade(&window_state);
903 let mut window_state_borrow = window_state.as_ref().borrow_mut();
904
905 let event =
906 unsafe { Event::from_native(native_event, Some(window_state_borrow.content_size().y())) };
907 if let Some(event) = event {
908 match &event {
909 Event::MouseMoved(
910 event @ MouseMovedEvent {
911 pressed_button: Some(_),
912 ..
913 },
914 ) => {
915 window_state_borrow.synthetic_drag_counter += 1;
916 window_state_borrow
917 .executor
918 .spawn(synthetic_drag(
919 weak_window_state,
920 window_state_borrow.synthetic_drag_counter,
921 *event,
922 ))
923 .detach();
924 }
925 Event::MouseUp(MouseButtonEvent {
926 button: MouseButton::Left,
927 ..
928 }) => {
929 window_state_borrow.synthetic_drag_counter += 1;
930 }
931 Event::ModifiersChanged(ModifiersChangedEvent {
932 ctrl,
933 alt,
934 shift,
935 cmd,
936 }) => {
937 // Only raise modifiers changed event when they have actually changed
938 if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
939 ctrl: prev_ctrl,
940 alt: prev_alt,
941 shift: prev_shift,
942 cmd: prev_cmd,
943 })) = &window_state_borrow.previous_modifiers_changed_event
944 {
945 if prev_ctrl == ctrl
946 && prev_alt == alt
947 && prev_shift == shift
948 && prev_cmd == cmd
949 {
950 return;
951 }
952 }
953
954 window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
955 }
956 _ => {}
957 }
958
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}
966
967// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
968// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
969extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
970 let window_state = unsafe { get_window_state(this) };
971 let mut window_state_borrow = window_state.as_ref().borrow_mut();
972
973 let keystroke = Keystroke {
974 cmd: true,
975 ctrl: false,
976 alt: false,
977 shift: false,
978 function: false,
979 key: ".".into(),
980 };
981 let event = Event::KeyDown(KeyDownEvent {
982 keystroke: keystroke.clone(),
983 is_held: false,
984 });
985
986 window_state_borrow.last_fresh_keydown = Some(keystroke);
987 if let Some(mut callback) = window_state_borrow.event_callback.take() {
988 drop(window_state_borrow);
989 callback(event);
990 window_state.borrow_mut().event_callback = Some(callback);
991 }
992}
993
994extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
995 unsafe {
996 let _: () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
997 get_window_state(this).borrow_mut().performed_key_equivalent = false;
998 }
999}
1000
1001extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
1002 let window_state = unsafe { get_window_state(this) };
1003 window_state.as_ref().borrow().move_traffic_light();
1004}
1005
1006extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
1007 window_fullscreen_changed(this, true);
1008}
1009
1010extern "C" fn window_will_exit_fullscreen(this: &Object, _: Sel, _: id) {
1011 window_fullscreen_changed(this, false);
1012}
1013
1014fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
1015 let window_state = unsafe { get_window_state(this) };
1016 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1017 if let Some(mut callback) = window_state_borrow.fullscreen_callback.take() {
1018 drop(window_state_borrow);
1019 callback(is_fullscreen);
1020 window_state.borrow_mut().fullscreen_callback = Some(callback);
1021 }
1022}
1023
1024extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
1025 let is_active = if selector == sel!(windowDidBecomeKey:) {
1026 true
1027 } else if selector == sel!(windowDidResignKey:) {
1028 false
1029 } else {
1030 unreachable!();
1031 };
1032
1033 let window_state = unsafe { get_window_state(this) };
1034 let executor = window_state.as_ref().borrow().executor.clone();
1035 executor
1036 .spawn(async move {
1037 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1038 if let Some(mut callback) = window_state_borrow.activate_callback.take() {
1039 drop(window_state_borrow);
1040 callback(is_active);
1041 window_state.borrow_mut().activate_callback = Some(callback);
1042 };
1043 })
1044 .detach();
1045}
1046
1047extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
1048 let window_state = unsafe { get_window_state(this) };
1049 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1050 if let Some(mut callback) = window_state_borrow.should_close_callback.take() {
1051 drop(window_state_borrow);
1052 let should_close = callback();
1053 window_state.borrow_mut().should_close_callback = Some(callback);
1054 should_close as BOOL
1055 } else {
1056 YES
1057 }
1058}
1059
1060extern "C" fn close_window(this: &Object, _: Sel) {
1061 unsafe {
1062 let close_callback = {
1063 let window_state = get_window_state(this);
1064 window_state
1065 .as_ref()
1066 .try_borrow_mut()
1067 .ok()
1068 .and_then(|mut window_state| window_state.close_callback.take())
1069 };
1070
1071 if let Some(callback) = close_callback {
1072 callback();
1073 }
1074
1075 let _: () = msg_send![super(this, class!(NSWindow)), close];
1076 }
1077}
1078
1079extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
1080 let window_state = unsafe { get_window_state(this) };
1081 let window_state = window_state.as_ref().borrow();
1082 window_state.renderer.layer().as_ptr() as id
1083}
1084
1085extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
1086 let window_state = unsafe { get_window_state(this) };
1087 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1088
1089 unsafe {
1090 let scale_factor = window_state_borrow.scale_factor() as f64;
1091 let size = window_state_borrow.content_size();
1092 let drawable_size: NSSize = NSSize {
1093 width: size.x() as f64 * scale_factor,
1094 height: size.y() as f64 * scale_factor,
1095 };
1096
1097 let _: () = msg_send![
1098 window_state_borrow.renderer.layer(),
1099 setContentsScale: scale_factor
1100 ];
1101 let _: () = msg_send![
1102 window_state_borrow.renderer.layer(),
1103 setDrawableSize: drawable_size
1104 ];
1105 }
1106
1107 if let Some(mut callback) = window_state_borrow.resize_callback.take() {
1108 drop(window_state_borrow);
1109 callback();
1110 window_state.as_ref().borrow_mut().resize_callback = Some(callback);
1111 };
1112}
1113
1114extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
1115 let window_state = unsafe { get_window_state(this) };
1116 let window_state_borrow = window_state.as_ref().borrow();
1117
1118 if window_state_borrow.content_size() == vec2f(size.width as f32, size.height as f32) {
1119 return;
1120 }
1121
1122 unsafe {
1123 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
1124 }
1125
1126 let scale_factor = window_state_borrow.scale_factor() as f64;
1127 let drawable_size: NSSize = NSSize {
1128 width: size.width * scale_factor,
1129 height: size.height * scale_factor,
1130 };
1131
1132 unsafe {
1133 let _: () = msg_send![
1134 window_state_borrow.renderer.layer(),
1135 setDrawableSize: drawable_size
1136 ];
1137 }
1138
1139 drop(window_state_borrow);
1140 let mut window_state_borrow = window_state.borrow_mut();
1141 if let Some(mut callback) = window_state_borrow.resize_callback.take() {
1142 drop(window_state_borrow);
1143 callback();
1144 window_state.borrow_mut().resize_callback = Some(callback);
1145 };
1146}
1147
1148extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1149 unsafe {
1150 let window_state = get_window_state(this);
1151 let mut window_state = window_state.as_ref().borrow_mut();
1152 if let Some(scene) = window_state.scene_to_render.take() {
1153 window_state.renderer.render(&scene);
1154 };
1155 }
1156}
1157
1158extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1159 unsafe { msg_send![class!(NSArray), array] }
1160}
1161
1162extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1163 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1164 .flatten()
1165 .is_some() as BOOL
1166}
1167
1168extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1169 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1170 .flatten()
1171 .map_or(NSRange::invalid(), |range| range.into())
1172}
1173
1174extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1175 with_input_handler(this, |input_handler| input_handler.selected_text_range())
1176 .flatten()
1177 .map_or(NSRange::invalid(), |range| range.into())
1178}
1179
1180extern "C" fn first_rect_for_character_range(
1181 this: &Object,
1182 _: Sel,
1183 range: NSRange,
1184 _: id,
1185) -> NSRect {
1186 let frame = unsafe {
1187 let window = get_window_state(this).borrow().native_window;
1188 NSView::frame(window)
1189 };
1190 with_input_handler(this, |input_handler| {
1191 input_handler.rect_for_range(range.to_range()?)
1192 })
1193 .flatten()
1194 .map_or(
1195 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1196 |rect| {
1197 NSRect::new(
1198 NSPoint::new(
1199 frame.origin.x + rect.origin_x() as f64,
1200 frame.origin.y + frame.size.height - rect.origin_y() as f64,
1201 ),
1202 NSSize::new(rect.width() as f64, rect.height() as f64),
1203 )
1204 },
1205 )
1206}
1207
1208extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1209 unsafe {
1210 let window_state = get_window_state(this);
1211 let mut window_state_borrow = window_state.borrow_mut();
1212 let pending_key_down = window_state_borrow.pending_key_down.take();
1213 drop(window_state_borrow);
1214
1215 let is_attributed_string: BOOL =
1216 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1217 let text: id = if is_attributed_string == YES {
1218 msg_send![text, string]
1219 } else {
1220 text
1221 };
1222 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1223 .to_str()
1224 .unwrap();
1225 let replacement_range = replacement_range.to_range();
1226
1227 window_state.borrow_mut().ime_text = Some(text.to_string());
1228 window_state.borrow_mut().ime_state = ImeState::Acted;
1229
1230 let is_composing =
1231 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1232 .flatten()
1233 .is_some();
1234
1235 if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
1236 with_input_handler(this, |input_handler| {
1237 input_handler.replace_text_in_range(replacement_range, text)
1238 });
1239 } else {
1240 let mut pending_key_down = pending_key_down.unwrap();
1241 pending_key_down.1 = Some(InsertText {
1242 replacement_range,
1243 text: text.to_string(),
1244 });
1245 window_state.borrow_mut().pending_key_down = Some(pending_key_down);
1246 }
1247 }
1248}
1249
1250extern "C" fn set_marked_text(
1251 this: &Object,
1252 _: Sel,
1253 text: id,
1254 selected_range: NSRange,
1255 replacement_range: NSRange,
1256) {
1257 unsafe {
1258 let window_state = get_window_state(this);
1259 window_state.borrow_mut().pending_key_down.take();
1260
1261 let is_attributed_string: BOOL =
1262 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1263 let text: id = if is_attributed_string == YES {
1264 msg_send![text, string]
1265 } else {
1266 text
1267 };
1268 let selected_range = selected_range.to_range();
1269 let replacement_range = replacement_range.to_range();
1270 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1271 .to_str()
1272 .unwrap();
1273
1274 window_state.borrow_mut().ime_state = ImeState::Acted;
1275 window_state.borrow_mut().ime_text = Some(text.to_string());
1276
1277 with_input_handler(this, |input_handler| {
1278 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
1279 });
1280 }
1281}
1282
1283extern "C" fn unmark_text(this: &Object, _: Sel) {
1284 unsafe {
1285 let state = get_window_state(this);
1286 let mut borrow = state.borrow_mut();
1287 borrow.ime_state = ImeState::Acted;
1288 borrow.ime_text.take();
1289 }
1290
1291 with_input_handler(this, |input_handler| input_handler.unmark_text());
1292}
1293
1294extern "C" fn attributed_substring_for_proposed_range(
1295 this: &Object,
1296 _: Sel,
1297 range: NSRange,
1298 _actual_range: *mut c_void,
1299) -> id {
1300 with_input_handler(this, |input_handler| {
1301 let range = range.to_range()?;
1302 if range.is_empty() {
1303 return None;
1304 }
1305
1306 let selected_text = input_handler.text_for_range(range)?;
1307 unsafe {
1308 let string: id = msg_send![class!(NSAttributedString), alloc];
1309 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
1310 Some(string)
1311 }
1312 })
1313 .flatten()
1314 .unwrap_or(nil)
1315}
1316
1317extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
1318 unsafe {
1319 let state = get_window_state(this);
1320 let mut borrow = state.borrow_mut();
1321 borrow.ime_state = ImeState::Continue;
1322 borrow.ime_text.take();
1323 }
1324}
1325
1326extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
1327 unsafe {
1328 let state = get_window_state(this);
1329 let mut state_borrow = state.as_ref().borrow_mut();
1330 if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
1331 drop(state_borrow);
1332 callback();
1333 state.borrow_mut().appearance_changed_callback = Some(callback);
1334 }
1335 }
1336}
1337
1338async fn synthetic_drag(
1339 window_state: Weak<RefCell<WindowState>>,
1340 drag_id: usize,
1341 event: MouseMovedEvent,
1342) {
1343 loop {
1344 Timer::after(Duration::from_millis(16)).await;
1345 if let Some(window_state) = window_state.upgrade() {
1346 let mut window_state_borrow = window_state.borrow_mut();
1347 if window_state_borrow.synthetic_drag_counter == drag_id {
1348 if let Some(mut callback) = window_state_borrow.event_callback.take() {
1349 drop(window_state_borrow);
1350 callback(Event::MouseMoved(event));
1351 window_state.borrow_mut().event_callback = Some(callback);
1352 }
1353 } else {
1354 break;
1355 }
1356 }
1357 }
1358}
1359
1360unsafe fn ns_string(string: &str) -> id {
1361 NSString::alloc(nil).init_str(string).autorelease()
1362}
1363
1364fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
1365where
1366 F: FnOnce(&mut dyn InputHandler) -> R,
1367{
1368 let window_state = unsafe { get_window_state(window) };
1369 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1370 if let Some(mut input_handler) = window_state_borrow.input_handler.take() {
1371 drop(window_state_borrow);
1372 let result = f(input_handler.as_mut());
1373 window_state.borrow_mut().input_handler = Some(input_handler);
1374 Some(result)
1375 } else {
1376 None
1377 }
1378}