window.rs

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