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