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
573impl platform::WindowContext for Window {
574 fn size(&self) -> Vector2F {
575 self.0.as_ref().borrow().size()
576 }
577
578 fn scale_factor(&self) -> f32 {
579 self.0.as_ref().borrow().scale_factor()
580 }
581
582 fn present_scene(&mut self, scene: Scene) {
583 self.0.as_ref().borrow_mut().present_scene(scene);
584 }
585
586 fn titlebar_height(&self) -> f32 {
587 self.0.as_ref().borrow().titlebar_height()
588 }
589}
590
591impl WindowState {
592 fn move_traffic_light(&self) {
593 if let Some(traffic_light_position) = self.traffic_light_position {
594 let titlebar_height = self.titlebar_height();
595
596 unsafe {
597 let close_button: id = msg_send![
598 self.native_window,
599 standardWindowButton: NSWindowButton::NSWindowCloseButton
600 ];
601 let min_button: id = msg_send![
602 self.native_window,
603 standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
604 ];
605 let zoom_button: id = msg_send![
606 self.native_window,
607 standardWindowButton: NSWindowButton::NSWindowZoomButton
608 ];
609
610 let mut close_button_frame: CGRect = msg_send![close_button, frame];
611 let mut min_button_frame: CGRect = msg_send![min_button, frame];
612 let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
613 let mut origin = vec2f(
614 traffic_light_position.x(),
615 titlebar_height
616 - traffic_light_position.y()
617 - close_button_frame.size.height as f32,
618 );
619 let button_spacing =
620 (min_button_frame.origin.x - close_button_frame.origin.x) as f32;
621
622 close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
623 let _: () = msg_send![close_button, setFrame: close_button_frame];
624 origin.set_x(origin.x() + button_spacing);
625
626 min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
627 let _: () = msg_send![min_button, setFrame: min_button_frame];
628 origin.set_x(origin.x() + button_spacing);
629
630 zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
631 let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
632 }
633 }
634 }
635}
636
637impl platform::WindowContext for WindowState {
638 fn size(&self) -> Vector2F {
639 let NSSize { width, height, .. } =
640 unsafe { NSView::frame(self.native_window.contentView()) }.size;
641 vec2f(width as f32, height as f32)
642 }
643
644 fn scale_factor(&self) -> f32 {
645 get_scale_factor(self.native_window)
646 }
647
648 fn titlebar_height(&self) -> f32 {
649 unsafe {
650 let frame = NSWindow::frame(self.native_window);
651 let content_layout_rect: CGRect = msg_send![self.native_window, contentLayoutRect];
652 (frame.size.height - content_layout_rect.size.height) as f32
653 }
654 }
655
656 fn present_scene(&mut self, scene: Scene) {
657 self.scene_to_render = Some(scene);
658 unsafe {
659 let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
660 }
661 }
662}
663
664fn get_scale_factor(native_window: id) -> f32 {
665 unsafe {
666 let screen: id = msg_send![native_window, screen];
667 NSScreen::backingScaleFactor(screen) as f32
668 }
669}
670
671unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
672 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
673 let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
674 let rc2 = rc1.clone();
675 mem::forget(rc1);
676 rc2
677}
678
679unsafe fn drop_window_state(object: &Object) {
680 let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
681 Rc::from_raw(raw as *mut RefCell<WindowState>);
682}
683
684extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
685 YES
686}
687
688extern "C" fn dealloc_window(this: &Object, _: Sel) {
689 unsafe {
690 drop_window_state(this);
691 let () = msg_send![super(this, class!(NSWindow)), dealloc];
692 }
693}
694
695extern "C" fn dealloc_view(this: &Object, _: Sel) {
696 unsafe {
697 drop_window_state(this);
698 let () = msg_send![super(this, class!(NSView)), dealloc];
699 }
700}
701
702extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
703 handle_key_event(this, native_event, true)
704}
705
706extern "C" fn handle_key_down(this: &Object, _: Sel, native_event: id) {
707 handle_key_event(this, native_event, false);
708}
709
710extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: bool) -> BOOL {
711 let window_state = unsafe { get_window_state(this) };
712
713 let mut window_state_borrow = window_state.as_ref().borrow_mut();
714
715 let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
716 if let Some(event) = event {
717 if key_equivalent {
718 window_state_borrow.performed_key_equivalent = true;
719 } else if window_state_borrow.performed_key_equivalent {
720 return NO;
721 }
722
723 let function_is_held;
724 window_state_borrow.pending_key_down = match event {
725 Event::KeyDown(event) => {
726 let keydown = event.keystroke.clone();
727 // Ignore events from held-down keys after some of the initially-pressed keys
728 // were released.
729 if event.is_held {
730 if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
731 return YES;
732 }
733 } else {
734 window_state_borrow.last_fresh_keydown = Some(keydown);
735 }
736
737 function_is_held = event.keystroke.function;
738 Some((event, None))
739 }
740 _ => return NO,
741 };
742 drop(window_state_borrow);
743
744 if !function_is_held {
745 unsafe {
746 let input_context: id = msg_send![this, inputContext];
747 let _: BOOL = msg_send![input_context, handleEvent: native_event];
748 }
749 }
750
751 let mut handled = false;
752 let mut window_state_borrow = window_state.borrow_mut();
753 if let Some((event, insert_text)) = window_state_borrow.pending_key_down.take() {
754 if let Some(mut callback) = window_state_borrow.event_callback.take() {
755 drop(window_state_borrow);
756
757 let is_composing =
758 with_input_handler(this, |input_handler| input_handler.marked_text_range())
759 .flatten()
760 .is_some();
761 if !is_composing {
762 handled = callback(Event::KeyDown(event));
763 }
764
765 if !handled {
766 if let Some(insert) = insert_text {
767 handled = true;
768 with_input_handler(this, |input_handler| {
769 input_handler
770 .replace_text_in_range(insert.replacement_range, &insert.text)
771 });
772 }
773 }
774
775 window_state.borrow_mut().event_callback = Some(callback);
776 }
777 } else {
778 handled = true;
779 }
780
781 handled as BOOL
782 } else {
783 NO
784 }
785}
786
787extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
788 let window_state = unsafe { get_window_state(this) };
789 let weak_window_state = Rc::downgrade(&window_state);
790 let mut window_state_borrow = window_state.as_ref().borrow_mut();
791
792 let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
793 if let Some(event) = event {
794 match &event {
795 Event::MouseMoved(
796 event @ MouseMovedEvent {
797 pressed_button: Some(_),
798 ..
799 },
800 ) => {
801 window_state_borrow.synthetic_drag_counter += 1;
802 window_state_borrow
803 .executor
804 .spawn(synthetic_drag(
805 weak_window_state,
806 window_state_borrow.synthetic_drag_counter,
807 *event,
808 ))
809 .detach();
810 }
811 Event::MouseUp(MouseButtonEvent {
812 button: MouseButton::Left,
813 ..
814 }) => {
815 window_state_borrow.synthetic_drag_counter += 1;
816 }
817 Event::ModifiersChanged(ModifiersChangedEvent {
818 ctrl,
819 alt,
820 shift,
821 cmd,
822 }) => {
823 // Only raise modifiers changed event when they have actually changed
824 if let Some(Event::ModifiersChanged(ModifiersChangedEvent {
825 ctrl: prev_ctrl,
826 alt: prev_alt,
827 shift: prev_shift,
828 cmd: prev_cmd,
829 })) = &window_state_borrow.previous_modifiers_changed_event
830 {
831 if prev_ctrl == ctrl
832 && prev_alt == alt
833 && prev_shift == shift
834 && prev_cmd == cmd
835 {
836 return;
837 }
838 }
839
840 window_state_borrow.previous_modifiers_changed_event = Some(event.clone());
841 }
842 _ => {}
843 }
844
845 if let Some(mut callback) = window_state_borrow.event_callback.take() {
846 drop(window_state_borrow);
847 callback(event);
848 window_state.borrow_mut().event_callback = Some(callback);
849 }
850 }
851}
852
853// Allows us to receive `cmd-.` (the shortcut for closing a dialog)
854// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
855extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
856 let window_state = unsafe { get_window_state(this) };
857 let mut window_state_borrow = window_state.as_ref().borrow_mut();
858
859 let keystroke = Keystroke {
860 cmd: true,
861 ctrl: false,
862 alt: false,
863 shift: false,
864 function: false,
865 key: ".".into(),
866 };
867 let event = Event::KeyDown(KeyDownEvent {
868 keystroke: keystroke.clone(),
869 is_held: false,
870 });
871
872 window_state_borrow.last_fresh_keydown = Some(keystroke);
873 if let Some(mut callback) = window_state_borrow.event_callback.take() {
874 drop(window_state_borrow);
875 callback(event);
876 window_state.borrow_mut().event_callback = Some(callback);
877 }
878}
879
880extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
881 unsafe {
882 let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
883 get_window_state(this).borrow_mut().performed_key_equivalent = false;
884 }
885}
886
887extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
888 let window_state = unsafe { get_window_state(this) };
889 window_state.as_ref().borrow().move_traffic_light();
890}
891
892extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
893 let is_active = if selector == sel!(windowDidBecomeKey:) {
894 true
895 } else if selector == sel!(windowDidResignKey:) {
896 false
897 } else {
898 unreachable!();
899 };
900
901 let window_state = unsafe { get_window_state(this) };
902 let executor = window_state.as_ref().borrow().executor.clone();
903 executor
904 .spawn(async move {
905 let mut window_state_borrow = window_state.as_ref().borrow_mut();
906 if let Some(mut callback) = window_state_borrow.activate_callback.take() {
907 drop(window_state_borrow);
908 callback(is_active);
909 window_state.borrow_mut().activate_callback = Some(callback);
910 };
911 })
912 .detach();
913}
914
915extern "C" fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL {
916 let window_state = unsafe { get_window_state(this) };
917 let mut window_state_borrow = window_state.as_ref().borrow_mut();
918 if let Some(mut callback) = window_state_borrow.should_close_callback.take() {
919 drop(window_state_borrow);
920 let should_close = callback();
921 window_state.borrow_mut().should_close_callback = Some(callback);
922 should_close as BOOL
923 } else {
924 YES
925 }
926}
927
928extern "C" fn close_window(this: &Object, _: Sel) {
929 unsafe {
930 let close_callback = {
931 let window_state = get_window_state(this);
932 window_state
933 .as_ref()
934 .try_borrow_mut()
935 .ok()
936 .and_then(|mut window_state| window_state.close_callback.take())
937 };
938
939 if let Some(callback) = close_callback {
940 callback();
941 }
942
943 let () = msg_send![super(this, class!(NSWindow)), close];
944 }
945}
946
947extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
948 let window_state = unsafe { get_window_state(this) };
949 let window_state = window_state.as_ref().borrow();
950 window_state.layer
951}
952
953extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
954 let window_state = unsafe { get_window_state(this) };
955 let mut window_state_borrow = window_state.as_ref().borrow_mut();
956
957 unsafe {
958 let scale_factor = window_state_borrow.scale_factor() as f64;
959 let size = window_state_borrow.size();
960 let drawable_size: NSSize = NSSize {
961 width: size.x() as f64 * scale_factor,
962 height: size.y() as f64 * scale_factor,
963 };
964
965 let _: () = msg_send![window_state_borrow.layer, setContentsScale: scale_factor];
966 let _: () = msg_send![window_state_borrow.layer, setDrawableSize: drawable_size];
967 }
968
969 if let Some(mut callback) = window_state_borrow.resize_callback.take() {
970 drop(window_state_borrow);
971 callback();
972 window_state.as_ref().borrow_mut().resize_callback = Some(callback);
973 };
974}
975
976extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
977 let window_state = unsafe { get_window_state(this) };
978 let window_state_borrow = window_state.as_ref().borrow();
979
980 if window_state_borrow.size() == vec2f(size.width as f32, size.height as f32) {
981 return;
982 }
983
984 unsafe {
985 let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
986 }
987
988 let scale_factor = window_state_borrow.scale_factor() as f64;
989 let drawable_size: NSSize = NSSize {
990 width: size.width * scale_factor,
991 height: size.height * scale_factor,
992 };
993
994 unsafe {
995 let _: () = msg_send![window_state_borrow.layer, setDrawableSize: drawable_size];
996 }
997
998 drop(window_state_borrow);
999 let mut window_state_borrow = window_state.borrow_mut();
1000 if let Some(mut callback) = window_state_borrow.resize_callback.take() {
1001 drop(window_state_borrow);
1002 callback();
1003 window_state.borrow_mut().resize_callback = Some(callback);
1004 };
1005}
1006
1007extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
1008 unsafe {
1009 let window_state = get_window_state(this);
1010 let mut window_state = window_state.as_ref().borrow_mut();
1011
1012 if let Some(scene) = window_state.scene_to_render.take() {
1013 let drawable: &metal::MetalDrawableRef = msg_send![window_state.layer, nextDrawable];
1014 let command_queue = window_state.command_queue.clone();
1015 let command_buffer = command_queue.new_command_buffer();
1016
1017 let size = window_state.size();
1018 let scale_factor = window_state.scale_factor();
1019
1020 window_state.renderer.render(
1021 &scene,
1022 size * scale_factor,
1023 command_buffer,
1024 drawable.texture(),
1025 );
1026
1027 command_buffer.commit();
1028 command_buffer.wait_until_completed();
1029 drawable.present();
1030 };
1031 }
1032}
1033
1034extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
1035 unsafe { msg_send![class!(NSArray), array] }
1036}
1037
1038extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
1039 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1040 .flatten()
1041 .is_some() as BOOL
1042}
1043
1044extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
1045 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1046 .flatten()
1047 .map_or(NSRange::invalid(), |range| range.into())
1048}
1049
1050extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
1051 with_input_handler(this, |input_handler| input_handler.selected_text_range())
1052 .flatten()
1053 .map_or(NSRange::invalid(), |range| range.into())
1054}
1055
1056extern "C" fn first_rect_for_character_range(
1057 this: &Object,
1058 _: Sel,
1059 range: NSRange,
1060 _: id,
1061) -> NSRect {
1062 let frame = unsafe {
1063 let window = get_window_state(this).borrow().native_window;
1064 NSView::frame(window)
1065 };
1066
1067 with_input_handler(this, |input_handler| {
1068 input_handler.rect_for_range(range.to_range()?)
1069 })
1070 .flatten()
1071 .map_or(
1072 NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
1073 |rect| {
1074 NSRect::new(
1075 NSPoint::new(
1076 frame.origin.x + rect.origin_x() as f64,
1077 frame.origin.y + frame.size.height - rect.origin_y() as f64,
1078 ),
1079 NSSize::new(rect.width() as f64, rect.height() as f64),
1080 )
1081 },
1082 )
1083}
1084
1085extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
1086 unsafe {
1087 let window_state = get_window_state(this);
1088 let mut window_state_borrow = window_state.borrow_mut();
1089 let pending_key_down = window_state_borrow.pending_key_down.take();
1090 drop(window_state_borrow);
1091
1092 let is_attributed_string: BOOL =
1093 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1094 let text: id = if is_attributed_string == YES {
1095 msg_send![text, string]
1096 } else {
1097 text
1098 };
1099 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1100 .to_str()
1101 .unwrap();
1102 let replacement_range = replacement_range.to_range();
1103
1104 let is_composing =
1105 with_input_handler(this, |input_handler| input_handler.marked_text_range())
1106 .flatten()
1107 .is_some();
1108
1109 if is_composing || text.chars().count() > 1 || pending_key_down.is_none() {
1110 with_input_handler(this, |input_handler| {
1111 input_handler.replace_text_in_range(replacement_range, text)
1112 });
1113 } else {
1114 let mut pending_key_down = pending_key_down.unwrap();
1115 pending_key_down.1 = Some(InsertText {
1116 replacement_range,
1117 text: text.to_string(),
1118 });
1119 window_state.borrow_mut().pending_key_down = Some(pending_key_down);
1120 }
1121 }
1122}
1123
1124extern "C" fn set_marked_text(
1125 this: &Object,
1126 _: Sel,
1127 text: id,
1128 selected_range: NSRange,
1129 replacement_range: NSRange,
1130) {
1131 unsafe {
1132 get_window_state(this).borrow_mut().pending_key_down.take();
1133
1134 let is_attributed_string: BOOL =
1135 msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
1136 let text: id = if is_attributed_string == YES {
1137 msg_send![text, string]
1138 } else {
1139 text
1140 };
1141 let selected_range = selected_range.to_range();
1142 let replacement_range = replacement_range.to_range();
1143 let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
1144 .to_str()
1145 .unwrap();
1146
1147 with_input_handler(this, |input_handler| {
1148 input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
1149 });
1150 }
1151}
1152
1153extern "C" fn unmark_text(this: &Object, _: Sel) {
1154 with_input_handler(this, |input_handler| input_handler.unmark_text());
1155}
1156
1157extern "C" fn attributed_substring_for_proposed_range(
1158 this: &Object,
1159 _: Sel,
1160 range: NSRange,
1161 _actual_range: *mut c_void,
1162) -> id {
1163 with_input_handler(this, |input_handler| {
1164 let range = range.to_range()?;
1165 if range.is_empty() {
1166 return None;
1167 }
1168
1169 let selected_text = input_handler.text_for_range(range)?;
1170 unsafe {
1171 let string: id = msg_send![class!(NSAttributedString), alloc];
1172 let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
1173 Some(string)
1174 }
1175 })
1176 .flatten()
1177 .unwrap_or(nil)
1178}
1179
1180extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {}
1181
1182async fn synthetic_drag(
1183 window_state: Weak<RefCell<WindowState>>,
1184 drag_id: usize,
1185 event: MouseMovedEvent,
1186) {
1187 loop {
1188 Timer::after(Duration::from_millis(16)).await;
1189 if let Some(window_state) = window_state.upgrade() {
1190 let mut window_state_borrow = window_state.borrow_mut();
1191 if window_state_borrow.synthetic_drag_counter == drag_id {
1192 if let Some(mut callback) = window_state_borrow.event_callback.take() {
1193 drop(window_state_borrow);
1194 callback(Event::MouseMoved(event));
1195 window_state.borrow_mut().event_callback = Some(callback);
1196 }
1197 } else {
1198 break;
1199 }
1200 }
1201 }
1202}
1203
1204unsafe fn ns_string(string: &str) -> id {
1205 NSString::alloc(nil).init_str(string).autorelease()
1206}
1207
1208fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
1209where
1210 F: FnOnce(&mut dyn InputHandler) -> R,
1211{
1212 let window_state = unsafe { get_window_state(window) };
1213 let mut window_state_borrow = window_state.as_ref().borrow_mut();
1214 if let Some(mut input_handler) = window_state_borrow.input_handler.take() {
1215 drop(window_state_borrow);
1216 let result = f(input_handler.as_mut());
1217 window_state.borrow_mut().input_handler = Some(input_handler);
1218 Some(result)
1219 } else {
1220 None
1221 }
1222}