window.rs

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