window.rs

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