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