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