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