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