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