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