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