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