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