window.rs

  1use crate::{
  2    executor,
  3    geometry::vector::Vector2F,
  4    keymap::Keystroke,
  5    platform::{self, Event, WindowContext},
  6    Scene,
  7};
  8use block::ConcreteBlock;
  9use cocoa::{
 10    appkit::{
 11        NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
 12        NSViewWidthSizable, NSWindow, NSWindowStyleMask,
 13    },
 14    base::{id, nil},
 15    foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString},
 16    quartzcore::AutoresizingMask,
 17};
 18use ctor::ctor;
 19use foreign_types::ForeignType as _;
 20use objc::{
 21    class,
 22    declare::ClassDecl,
 23    msg_send,
 24    runtime::{Class, Object, Protocol, Sel, BOOL, NO, YES},
 25    sel, sel_impl,
 26};
 27use pathfinder_geometry::vector::vec2f;
 28use smol::Timer;
 29use std::{
 30    cell::{Cell, RefCell},
 31    convert::TryInto,
 32    ffi::c_void,
 33    mem, ptr,
 34    rc::{Rc, Weak},
 35    sync::Arc,
 36    time::Duration,
 37};
 38
 39use super::{geometry::RectFExt, renderer::Renderer};
 40
 41const WINDOW_STATE_IVAR: &'static str = "windowState";
 42
 43static mut WINDOW_CLASS: *const Class = ptr::null();
 44static mut VIEW_CLASS: *const Class = ptr::null();
 45
 46#[allow(non_upper_case_globals)]
 47const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
 48
 49#[ctor]
 50unsafe fn build_classes() {
 51    WINDOW_CLASS = {
 52        let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
 53        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
 54        decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
 55        decl.add_method(
 56            sel!(canBecomeMainWindow),
 57            yes as extern "C" fn(&Object, Sel) -> BOOL,
 58        );
 59        decl.add_method(
 60            sel!(canBecomeKeyWindow),
 61            yes as extern "C" fn(&Object, Sel) -> BOOL,
 62        );
 63        decl.add_method(
 64            sel!(sendEvent:),
 65            send_event as extern "C" fn(&Object, Sel, id),
 66        );
 67        decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
 68        decl.register()
 69    };
 70
 71    VIEW_CLASS = {
 72        let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
 73        decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
 74
 75        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
 76
 77        decl.add_method(
 78            sel!(keyDown:),
 79            handle_view_event as extern "C" fn(&Object, Sel, id),
 80        );
 81        decl.add_method(
 82            sel!(mouseDown:),
 83            handle_view_event as extern "C" fn(&Object, Sel, id),
 84        );
 85        decl.add_method(
 86            sel!(mouseUp:),
 87            handle_view_event as extern "C" fn(&Object, Sel, id),
 88        );
 89        decl.add_method(
 90            sel!(mouseMoved:),
 91            handle_view_event as extern "C" fn(&Object, Sel, id),
 92        );
 93        decl.add_method(
 94            sel!(mouseDragged:),
 95            handle_view_event as extern "C" fn(&Object, Sel, id),
 96        );
 97        decl.add_method(
 98            sel!(scrollWheel:),
 99            handle_view_event as extern "C" fn(&Object, Sel, id),
100        );
101
102        decl.add_method(
103            sel!(makeBackingLayer),
104            make_backing_layer as extern "C" fn(&Object, Sel) -> id,
105        );
106
107        decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
108        decl.add_method(
109            sel!(viewDidChangeBackingProperties),
110            view_did_change_backing_properties as extern "C" fn(&Object, Sel),
111        );
112        decl.add_method(
113            sel!(setFrameSize:),
114            set_frame_size as extern "C" fn(&Object, Sel, NSSize),
115        );
116        decl.add_method(
117            sel!(displayLayer:),
118            display_layer as extern "C" fn(&Object, Sel, id),
119        );
120
121        decl.register()
122    };
123}
124
125pub struct Window(Rc<RefCell<WindowState>>);
126
127struct WindowState {
128    id: usize,
129    native_window: id,
130    event_callback: Option<Box<dyn FnMut(Event)>>,
131    resize_callback: Option<Box<dyn FnMut(&mut dyn platform::WindowContext)>>,
132    close_callback: Option<Box<dyn FnOnce()>>,
133    synthetic_drag_counter: usize,
134    executor: Rc<executor::Foreground>,
135    scene_to_render: Option<Scene>,
136    renderer: Renderer,
137    command_queue: metal::CommandQueue,
138    last_fresh_keydown: Option<(Keystroke, String)>,
139    layer: id,
140}
141
142impl Window {
143    pub fn open(
144        id: usize,
145        options: platform::WindowOptions,
146        executor: Rc<executor::Foreground>,
147        fonts: Arc<dyn platform::FontSystem>,
148    ) -> Self {
149        const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
150
151        unsafe {
152            let pool = NSAutoreleasePool::new(nil);
153
154            let frame = options.bounds.to_ns_rect();
155            let style_mask = NSWindowStyleMask::NSClosableWindowMask
156                | NSWindowStyleMask::NSMiniaturizableWindowMask
157                | NSWindowStyleMask::NSResizableWindowMask
158                | NSWindowStyleMask::NSTitledWindowMask;
159
160            let native_window: id = msg_send![WINDOW_CLASS, alloc];
161            let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
162                frame,
163                style_mask,
164                NSBackingStoreBuffered,
165                NO,
166            );
167            assert!(!native_window.is_null());
168
169            let device =
170                metal::Device::system_default().expect("could not find default metal device");
171
172            let layer: id = msg_send![class!(CAMetalLayer), layer];
173            let _: () = msg_send![layer, setDevice: device.as_ptr()];
174            let _: () = msg_send![layer, setPixelFormat: PIXEL_FORMAT];
175            let _: () = msg_send![layer, setAllowsNextDrawableTimeout: NO];
176            let _: () = msg_send![layer, setNeedsDisplayOnBoundsChange: YES];
177            let _: () = msg_send![layer, setPresentsWithTransaction: YES];
178            let _: () = msg_send![
179                layer,
180                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
181                    | AutoresizingMask::HEIGHT_SIZABLE
182            ];
183
184            let native_view: id = msg_send![VIEW_CLASS, alloc];
185            let native_view = NSView::init(native_view);
186            assert!(!native_view.is_null());
187
188            let window = Self(Rc::new(RefCell::new(WindowState {
189                id,
190                native_window,
191                event_callback: None,
192                resize_callback: None,
193                close_callback: None,
194                synthetic_drag_counter: 0,
195                executor,
196                scene_to_render: Default::default(),
197                renderer: Renderer::new(device.clone(), PIXEL_FORMAT, fonts),
198                command_queue: device.new_command_queue(),
199                last_fresh_keydown: None,
200                layer,
201            })));
202
203            (*native_window).set_ivar(
204                WINDOW_STATE_IVAR,
205                Rc::into_raw(window.0.clone()) as *const c_void,
206            );
207            (*native_view).set_ivar(
208                WINDOW_STATE_IVAR,
209                Rc::into_raw(window.0.clone()) as *const c_void,
210            );
211
212            if let Some(title) = options.title.as_ref() {
213                native_window.setTitle_(NSString::alloc(nil).init_str(title));
214            }
215            native_window.setAcceptsMouseMovedEvents_(YES);
216
217            native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
218            native_view.setWantsBestResolutionOpenGLSurface_(YES);
219
220            // From winit crate: On Mojave, views automatically become layer-backed shortly after
221            // being added to a native_window. Changing the layer-backedness of a view breaks the
222            // association between the view and its associated OpenGL context. To work around this,
223            // on we explicitly make the view layer-backed up front so that AppKit doesn't do it
224            // itself and break the association with its context.
225            native_view.setWantsLayer(YES);
226            let _: () = msg_send![
227                native_view,
228                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
229            ];
230
231            native_window.setContentView_(native_view.autorelease());
232            native_window.makeFirstResponder_(native_view);
233
234            native_window.center();
235            native_window.makeKeyAndOrderFront_(nil);
236
237            pool.drain();
238
239            window
240        }
241    }
242
243    pub fn key_window_id() -> Option<usize> {
244        unsafe {
245            let app = NSApplication::sharedApplication(nil);
246            let key_window: id = msg_send![app, keyWindow];
247            if key_window.is_null() {
248                None
249            } else {
250                let id = get_window_state(&*key_window).borrow().id;
251                Some(id)
252            }
253        }
254    }
255}
256
257impl Drop for Window {
258    fn drop(&mut self) {
259        unsafe {
260            self.0.as_ref().borrow().native_window.close();
261        }
262    }
263}
264
265impl platform::Window for Window {
266    fn on_event(&mut self, callback: Box<dyn FnMut(Event)>) {
267        self.0.as_ref().borrow_mut().event_callback = Some(callback);
268    }
269
270    fn on_resize(&mut self, callback: Box<dyn FnMut(&mut dyn platform::WindowContext)>) {
271        self.0.as_ref().borrow_mut().resize_callback = Some(callback);
272    }
273
274    fn on_close(&mut self, callback: Box<dyn FnOnce()>) {
275        self.0.as_ref().borrow_mut().close_callback = Some(callback);
276    }
277
278    fn prompt(
279        &self,
280        level: platform::PromptLevel,
281        msg: &str,
282        answers: &[&str],
283        done_fn: Box<dyn FnOnce(usize)>,
284    ) {
285        unsafe {
286            let alert: id = msg_send![class!(NSAlert), alloc];
287            let alert: id = msg_send![alert, init];
288            let alert_style = match level {
289                platform::PromptLevel::Info => 1,
290                platform::PromptLevel::Warning => 0,
291                platform::PromptLevel::Critical => 2,
292            };
293            let _: () = msg_send![alert, setAlertStyle: alert_style];
294            let _: () = msg_send![alert, setMessageText: ns_string(msg)];
295            for (ix, answer) in answers.into_iter().enumerate() {
296                let button: id = msg_send![alert, addButtonWithTitle: ns_string(answer)];
297                let _: () = msg_send![button, setTag: ix as NSInteger];
298            }
299            let done_fn = Cell::new(Some(done_fn));
300            let block = ConcreteBlock::new(move |answer: NSInteger| {
301                if let Some(done_fn) = done_fn.take() {
302                    (done_fn)(answer.try_into().unwrap());
303                }
304            });
305            let block = block.copy();
306            let _: () = msg_send![
307                alert,
308                beginSheetModalForWindow: self.0.borrow().native_window
309                completionHandler: block
310            ];
311        }
312    }
313}
314
315impl platform::WindowContext for Window {
316    fn size(&self) -> Vector2F {
317        self.0.as_ref().borrow().size()
318    }
319
320    fn scale_factor(&self) -> f32 {
321        self.0.as_ref().borrow().scale_factor()
322    }
323
324    fn present_scene(&mut self, scene: Scene) {
325        self.0.as_ref().borrow_mut().present_scene(scene);
326    }
327}
328
329impl platform::WindowContext for WindowState {
330    fn size(&self) -> Vector2F {
331        let NSSize { width, height, .. } =
332            unsafe { NSView::frame(self.native_window.contentView()) }.size;
333        vec2f(width as f32, height as f32)
334    }
335
336    fn scale_factor(&self) -> f32 {
337        unsafe {
338            let screen: id = msg_send![self.native_window, screen];
339            NSScreen::backingScaleFactor(screen) as f32
340        }
341    }
342
343    fn present_scene(&mut self, scene: Scene) {
344        self.scene_to_render = Some(scene);
345        unsafe {
346            let _: () = msg_send![self.native_window.contentView(), setNeedsDisplay: YES];
347        }
348    }
349}
350
351unsafe fn get_window_state(object: &Object) -> Rc<RefCell<WindowState>> {
352    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
353    let rc1 = Rc::from_raw(raw as *mut RefCell<WindowState>);
354    let rc2 = rc1.clone();
355    mem::forget(rc1);
356    rc2
357}
358
359unsafe fn drop_window_state(object: &Object) {
360    let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
361    Rc::from_raw(raw as *mut RefCell<WindowState>);
362}
363
364extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
365    YES
366}
367
368extern "C" fn dealloc_window(this: &Object, _: Sel) {
369    unsafe {
370        drop_window_state(this);
371        let () = msg_send![super(this, class!(NSWindow)), dealloc];
372    }
373}
374
375extern "C" fn dealloc_view(this: &Object, _: Sel) {
376    unsafe {
377        drop_window_state(this);
378        let () = msg_send![super(this, class!(NSView)), dealloc];
379    }
380}
381
382extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
383    let window_state = unsafe { get_window_state(this) };
384    let weak_window_state = Rc::downgrade(&window_state);
385    let mut window_state_borrow = window_state.as_ref().borrow_mut();
386
387    let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
388
389    if let Some(event) = event {
390        match &event {
391            Event::LeftMouseDragged { position } => {
392                window_state_borrow.synthetic_drag_counter += 1;
393                window_state_borrow
394                    .executor
395                    .spawn(synthetic_drag(
396                        weak_window_state,
397                        window_state_borrow.synthetic_drag_counter,
398                        *position,
399                    ))
400                    .detach();
401            }
402            Event::LeftMouseUp { .. } => {
403                window_state_borrow.synthetic_drag_counter += 1;
404            }
405
406            // Ignore events from held-down keys after some of the initially-pressed keys
407            // were released.
408            Event::KeyDown {
409                chars,
410                keystroke,
411                is_held,
412            } => {
413                let keydown = (keystroke.clone(), chars.clone());
414                if *is_held {
415                    if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
416                        return;
417                    }
418                } else {
419                    window_state_borrow.last_fresh_keydown = Some(keydown);
420                }
421            }
422
423            _ => {}
424        }
425
426        if let Some(mut callback) = window_state_borrow.event_callback.take() {
427            drop(window_state_borrow);
428            callback(event);
429            window_state.borrow_mut().event_callback = Some(callback);
430        }
431    }
432}
433
434extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
435    unsafe {
436        let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
437    }
438}
439
440extern "C" fn close_window(this: &Object, _: Sel) {
441    unsafe {
442        let close_callback = {
443            let window_state = get_window_state(this);
444            window_state
445                .as_ref()
446                .try_borrow_mut()
447                .ok()
448                .and_then(|mut window_state| window_state.close_callback.take())
449        };
450
451        if let Some(callback) = close_callback {
452            callback();
453        }
454
455        let () = msg_send![super(this, class!(NSWindow)), close];
456    }
457}
458
459extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
460    let window_state = unsafe { get_window_state(this) };
461    let window_state = window_state.as_ref().borrow();
462    window_state.layer
463}
464
465extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel) {
466    let window_state = unsafe { get_window_state(this) };
467    let mut window_state = window_state.as_ref().borrow_mut();
468
469    unsafe {
470        let _: () =
471            msg_send![window_state.layer, setContentsScale: window_state.scale_factor() as f64];
472    }
473
474    if let Some(mut callback) = window_state.resize_callback.take() {
475        callback(&mut *window_state);
476        window_state.resize_callback = Some(callback);
477    };
478}
479
480extern "C" fn set_frame_size(this: &Object, _: Sel, size: NSSize) {
481    let window_state = unsafe { get_window_state(this) };
482    let mut window_state = window_state.as_ref().borrow_mut();
483
484    if window_state.size() == vec2f(size.width as f32, size.height as f32) {
485        return;
486    }
487
488    unsafe {
489        let _: () = msg_send![super(this, class!(NSView)), setFrameSize: size];
490    }
491
492    let scale_factor = window_state.scale_factor() as f64;
493    let drawable_size: NSSize = NSSize {
494        width: size.width * scale_factor,
495        height: size.height * scale_factor,
496    };
497
498    unsafe {
499        let _: () = msg_send![window_state.layer, setDrawableSize: drawable_size];
500    }
501
502    if let Some(mut callback) = window_state.resize_callback.take() {
503        callback(&mut *window_state);
504        window_state.resize_callback = Some(callback);
505    };
506}
507
508extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
509    unsafe {
510        let window_state = get_window_state(this);
511        let mut window_state = window_state.as_ref().borrow_mut();
512
513        if let Some(scene) = window_state.scene_to_render.take() {
514            let drawable: &metal::MetalDrawableRef = msg_send![window_state.layer, nextDrawable];
515            let command_queue = window_state.command_queue.clone();
516            let command_buffer = command_queue.new_command_buffer();
517
518            let size = window_state.size();
519            let scale_factor = window_state.scale_factor();
520
521            window_state.renderer.render(
522                &scene,
523                size * scale_factor,
524                command_buffer,
525                drawable.texture(),
526            );
527
528            command_buffer.commit();
529            command_buffer.wait_until_completed();
530            drawable.present();
531        };
532    }
533}
534
535async fn synthetic_drag(
536    window_state: Weak<RefCell<WindowState>>,
537    drag_id: usize,
538    position: Vector2F,
539) {
540    loop {
541        Timer::after(Duration::from_millis(16)).await;
542        if let Some(window_state) = window_state.upgrade() {
543            let mut window_state_borrow = window_state.borrow_mut();
544            if window_state_borrow.synthetic_drag_counter == drag_id {
545                if let Some(mut callback) = window_state_borrow.event_callback.take() {
546                    drop(window_state_borrow);
547                    callback(Event::LeftMouseDragged { position });
548                    window_state.borrow_mut().event_callback = Some(callback);
549                }
550            } else {
551                break;
552            }
553        }
554    }
555}
556
557unsafe fn ns_string(string: &str) -> id {
558    NSString::alloc(nil).init_str(string).autorelease()
559}