window.rs

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