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