macOS: basic event handling (#52)
* macOS: add basic event handling * macos: don't store subview pointer in WindowHandle * macOS: mention inspiration from antonok's vst_window crate, clean up * Add Anton Lazarev and myself to author list * macOS: fix event handling issues - Rename EventDelegate to WindowState - Make Window.ns_window optional, only set it if parentless - Put our own NSView subclass in Window.ns_view - Don't create useless "intermediate" NSView in parentless mode * macOS: use Arc::from_raw in WindowHandler dealloc fn * macOS: move subview code own file, handle more mouse events * macOS: add (non-tested) support for AsIfParented window * macOS: rename subview module to view * macOS: rename "mouse_click_extern_fn!" to "mouse_button_extern_fn!" This avoids confusion with the click event * macOS: make WindowState Arc wrapping code clearer * macOS: handle basic key press and release events * macOS: accept mouseMoved events, don't trigger them on clicks * macOS: fix cursor movement location conversion * macOS: add WindowState.trigger_event fn, make fields private * macOS: in view, set preservesContentInLiveResize to NO * macOS: add NSTrackingArea, cursor enter/exit events, better window init * macOS: remove unused WindowState.size field * macOS: acceptFirstMouse = YES in view * macOS: rename macro mouse_button_extern_fn to mouse_simple_extern_fn * macOS: remove key event handling, it will be implemented differently * macOS: trigger CursorMoved on right and middle mouse drag * macOS: run NSEvent.setMouseCoalescingEnabled(NO) * macOS: clean up * macOS: non-parented mode: don't "activate ignoring other apps" This is rarely necessary according to https://developer.apple.com/documentation/appkit/nsapplication/1428468-activate and I don't see any reason why we would need to do it. * macOS: call NSApp() before doing more work in non-parented mode * macOS: don't attempt to declare NSView subclass multiple times * macOS: add random suffix to name of NSView subclass to prevent issues * macOS: send tracking area options as a usize (objc UInt) * macOS: use UUID for class name suffix * macOS: fix view_will_move_to_window super call * macOS: drop WindowState when our NSView is released * macOS: in Window::open, autorelease an NSString that was allocated * macOS: delete our view class when the view is released * Upgrade cocoa dependency to version 0.24.0 * macOS: reorder some code in view.rs * macOS: mark WindowState::from_field as unsafe, update doc comment * macOS: in HasRawWindowHandle impl, use unwrap_or for ns_window
This commit is contained in:
parent
cd5c91f8a8
commit
2167a091fa
|
@ -7,6 +7,8 @@ authors = [
|
||||||
"Mirko Covizzi <mrkcvzz@gmail.com>",
|
"Mirko Covizzi <mrkcvzz@gmail.com>",
|
||||||
"Micah Johnston <micah@glowcoil.com>",
|
"Micah Johnston <micah@glowcoil.com>",
|
||||||
"Billy Messenger <billydm@protonmail.com>",
|
"Billy Messenger <billydm@protonmail.com>",
|
||||||
|
"Anton Lazarev <https://antonok.com>",
|
||||||
|
"Joakim Frostegård <joakim.frostegard@gmail.com>",
|
||||||
]
|
]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -25,5 +27,6 @@ nix = "0.18"
|
||||||
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] }
|
winapi = { version = "0.3.8", features = ["libloaderapi", "winuser", "windef", "minwindef", "guiddef", "combaseapi", "wingdi", "errhandlingapi"] }
|
||||||
|
|
||||||
[target.'cfg(target_os="macos")'.dependencies]
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
cocoa = "0.23.0"
|
cocoa = "0.24.0"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
|
uuid = { version = "0.8", features = ["v4"] }
|
|
@ -1,2 +1,4 @@
|
||||||
mod window;
|
mod window;
|
||||||
|
mod view;
|
||||||
|
|
||||||
pub use window::*;
|
pub use window::*;
|
||||||
|
|
336
src/macos/view.rs
Normal file
336
src/macos/view.rs
Normal file
|
@ -0,0 +1,336 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use cocoa::appkit::{NSEvent, NSView};
|
||||||
|
use cocoa::base::{id, nil, BOOL, YES, NO};
|
||||||
|
use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize};
|
||||||
|
|
||||||
|
use objc::{
|
||||||
|
class,
|
||||||
|
declare::ClassDecl,
|
||||||
|
msg_send,
|
||||||
|
runtime::{Class, Object, Sel},
|
||||||
|
sel, sel_impl,
|
||||||
|
};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Event, MouseButton, MouseEvent, Point, WindowHandler,
|
||||||
|
WindowOpenOptions
|
||||||
|
};
|
||||||
|
use crate::MouseEvent::{ButtonPressed, ButtonReleased};
|
||||||
|
|
||||||
|
use super::window::{WindowState, WINDOW_STATE_IVAR_NAME};
|
||||||
|
|
||||||
|
|
||||||
|
pub(super) unsafe fn create_view<H: WindowHandler>(
|
||||||
|
window_options: &WindowOpenOptions,
|
||||||
|
) -> id {
|
||||||
|
let class = create_view_class::<H>();
|
||||||
|
|
||||||
|
let view: id = msg_send![class, alloc];
|
||||||
|
|
||||||
|
let size = window_options.size;
|
||||||
|
|
||||||
|
view.initWithFrame_(NSRect::new(
|
||||||
|
NSPoint::new(0., 0.),
|
||||||
|
NSSize::new(size.width, size.height),
|
||||||
|
));
|
||||||
|
|
||||||
|
view
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
unsafe fn create_view_class<H: WindowHandler>() -> &'static Class {
|
||||||
|
// Use unique class names so that there are no conflicts between different
|
||||||
|
// instances. The class is deleted when the view is released. Previously,
|
||||||
|
// the class was stored in a OnceCell after creation. This way, we didn't
|
||||||
|
// have to recreate it each time a view was opened, but now we don't leave
|
||||||
|
// any class definitions lying around when the plugin is closed.
|
||||||
|
let class_name = format!("BaseviewNSView_{}", Uuid::new_v4().to_simple());
|
||||||
|
let mut class = ClassDecl::new(&class_name, class!(NSView)).unwrap();
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(acceptsFirstResponder),
|
||||||
|
property_yes::<H> as extern "C" fn(&Object, Sel) -> BOOL
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(isFlipped),
|
||||||
|
property_yes::<H> as extern "C" fn(&Object, Sel) -> BOOL
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(preservesContentInLiveResize),
|
||||||
|
property_no::<H> as extern "C" fn(&Object, Sel) -> BOOL
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(acceptsFirstMouse:),
|
||||||
|
accepts_first_mouse::<H> as extern "C" fn(&Object, Sel, id) -> BOOL
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(release),
|
||||||
|
release::<H> as extern "C" fn(&Object, Sel)
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(viewWillMoveToWindow:),
|
||||||
|
view_will_move_to_window::<H> as extern "C" fn(&Object, Sel, id)
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(updateTrackingAreas:),
|
||||||
|
update_tracking_areas::<H> as extern "C" fn(&Object, Sel, id)
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(mouseMoved:),
|
||||||
|
mouse_moved::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(mouseDragged:),
|
||||||
|
mouse_moved::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(rightMouseDragged:),
|
||||||
|
mouse_moved::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(otherMouseDragged:),
|
||||||
|
mouse_moved::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(mouseEntered:),
|
||||||
|
mouse_entered::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(mouseExited:),
|
||||||
|
mouse_exited::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(mouseDown:),
|
||||||
|
left_mouse_down::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(mouseUp:),
|
||||||
|
left_mouse_up::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(rightMouseDown:),
|
||||||
|
right_mouse_down::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(rightMouseUp:),
|
||||||
|
right_mouse_up::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_method(
|
||||||
|
sel!(otherMouseDown:),
|
||||||
|
middle_mouse_down::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(otherMouseUp:),
|
||||||
|
middle_mouse_up::<H> as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR_NAME);
|
||||||
|
|
||||||
|
class.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn property_yes<H: WindowHandler>(
|
||||||
|
_this: &Object,
|
||||||
|
_sel: Sel,
|
||||||
|
) -> BOOL {
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn property_no<H: WindowHandler>(
|
||||||
|
_this: &Object,
|
||||||
|
_sel: Sel,
|
||||||
|
) -> BOOL {
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn accepts_first_mouse<H: WindowHandler>(
|
||||||
|
_this: &Object,
|
||||||
|
_sel: Sel,
|
||||||
|
_event: id
|
||||||
|
) -> BOOL {
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn release<H: WindowHandler>(this: &Object, _sel: Sel) {
|
||||||
|
unsafe {
|
||||||
|
let superclass = msg_send![this, superclass];
|
||||||
|
|
||||||
|
let () = msg_send![super(this, superclass), release];
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let retain_count: usize = msg_send![this, retainCount];
|
||||||
|
|
||||||
|
if retain_count == 1 {
|
||||||
|
let state_ptr: *mut c_void = *this.get_ivar(
|
||||||
|
WINDOW_STATE_IVAR_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
// Drop WindowState
|
||||||
|
Arc::from_raw(state_ptr as *mut WindowState<H>);
|
||||||
|
|
||||||
|
// Delete class
|
||||||
|
let class = msg_send![this, class];
|
||||||
|
::objc::runtime::objc_disposeClassPair(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Init/reinit tracking area
|
||||||
|
///
|
||||||
|
/// Info:
|
||||||
|
/// https://developer.apple.com/documentation/appkit/nstrackingarea
|
||||||
|
/// https://developer.apple.com/documentation/appkit/nstrackingarea/options
|
||||||
|
/// https://developer.apple.com/documentation/appkit/nstrackingareaoptions
|
||||||
|
unsafe fn reinit_tracking_area(this: &Object, tracking_area: *mut Object){
|
||||||
|
let options: usize = {
|
||||||
|
let mouse_entered_and_exited = 0x01;
|
||||||
|
let tracking_mouse_moved = 0x02;
|
||||||
|
let tracking_cursor_update = 0x04;
|
||||||
|
let tracking_active_in_active_app = 0x40;
|
||||||
|
let tracking_in_visible_rect = 0x200;
|
||||||
|
let tracking_enabled_during_mouse_drag = 0x400;
|
||||||
|
|
||||||
|
mouse_entered_and_exited | tracking_mouse_moved |
|
||||||
|
tracking_cursor_update | tracking_active_in_active_app |
|
||||||
|
tracking_in_visible_rect | tracking_enabled_during_mouse_drag
|
||||||
|
};
|
||||||
|
|
||||||
|
let bounds: NSRect = msg_send![this, bounds];
|
||||||
|
|
||||||
|
*tracking_area = msg_send![tracking_area,
|
||||||
|
initWithRect:bounds
|
||||||
|
options:options
|
||||||
|
owner:this
|
||||||
|
userInfo:nil
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn view_will_move_to_window<H: WindowHandler>(
|
||||||
|
this: &Object,
|
||||||
|
_self: Sel,
|
||||||
|
new_window: id
|
||||||
|
){
|
||||||
|
unsafe {
|
||||||
|
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
|
||||||
|
let tracking_area_count = NSArray::count(tracking_areas);
|
||||||
|
|
||||||
|
let _: () = msg_send![class!(NSEvent), setMouseCoalescingEnabled:NO];
|
||||||
|
|
||||||
|
if new_window == nil {
|
||||||
|
if tracking_area_count != 0 {
|
||||||
|
let tracking_area = NSArray::objectAtIndex(tracking_areas, 0);
|
||||||
|
|
||||||
|
|
||||||
|
let _: () = msg_send![this, removeTrackingArea:tracking_area];
|
||||||
|
let _: () = msg_send![tracking_area, release];
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if tracking_area_count == 0 {
|
||||||
|
let class = Class::get("NSTrackingArea").unwrap();
|
||||||
|
|
||||||
|
let tracking_area: *mut Object = msg_send![class, alloc];
|
||||||
|
|
||||||
|
reinit_tracking_area(this, tracking_area);
|
||||||
|
|
||||||
|
let _: () = msg_send![this, addTrackingArea:tracking_area];
|
||||||
|
}
|
||||||
|
|
||||||
|
let _: () = msg_send![new_window, setAcceptsMouseMovedEvents:YES];
|
||||||
|
let _: () = msg_send![new_window, makeFirstResponder:this];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let superclass = msg_send![this, superclass];
|
||||||
|
|
||||||
|
let () = msg_send![super(this, superclass), viewWillMoveToWindow:new_window];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn update_tracking_areas<H: WindowHandler>(
|
||||||
|
this: &Object,
|
||||||
|
_self: Sel,
|
||||||
|
_: id
|
||||||
|
){
|
||||||
|
unsafe {
|
||||||
|
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
|
||||||
|
let tracking_area = NSArray::objectAtIndex(tracking_areas, 0);
|
||||||
|
|
||||||
|
reinit_tracking_area(this, tracking_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C" fn mouse_moved<H: WindowHandler>(
|
||||||
|
this: &Object,
|
||||||
|
_sel: Sel,
|
||||||
|
event: id
|
||||||
|
){
|
||||||
|
let point: NSPoint = unsafe {
|
||||||
|
let point = NSEvent::locationInWindow(event);
|
||||||
|
|
||||||
|
msg_send![this, convertPoint:point fromView:nil]
|
||||||
|
};
|
||||||
|
|
||||||
|
let position = Point {
|
||||||
|
x: point.x,
|
||||||
|
y: point.y
|
||||||
|
};
|
||||||
|
|
||||||
|
let event = Event::Mouse(MouseEvent::CursorMoved { position });
|
||||||
|
|
||||||
|
let state: &mut WindowState<H> = unsafe {
|
||||||
|
WindowState::from_field(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
state.trigger_event(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
macro_rules! mouse_simple_extern_fn {
|
||||||
|
($fn:ident, $event:expr) => {
|
||||||
|
extern "C" fn $fn<H: WindowHandler>(
|
||||||
|
this: &Object,
|
||||||
|
_sel: Sel,
|
||||||
|
_event: id,
|
||||||
|
){
|
||||||
|
let state: &mut WindowState<H> = unsafe {
|
||||||
|
WindowState::from_field(this)
|
||||||
|
};
|
||||||
|
|
||||||
|
state.trigger_event(Event::Mouse($event));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mouse_simple_extern_fn!(left_mouse_down, ButtonPressed(MouseButton::Left));
|
||||||
|
mouse_simple_extern_fn!(left_mouse_up, ButtonReleased(MouseButton::Left));
|
||||||
|
|
||||||
|
mouse_simple_extern_fn!(right_mouse_down, ButtonPressed(MouseButton::Right));
|
||||||
|
mouse_simple_extern_fn!(right_mouse_up, ButtonReleased(MouseButton::Right));
|
||||||
|
|
||||||
|
mouse_simple_extern_fn!(middle_mouse_down, ButtonPressed(MouseButton::Middle));
|
||||||
|
mouse_simple_extern_fn!(middle_mouse_up, ButtonReleased(MouseButton::Middle));
|
||||||
|
|
||||||
|
mouse_simple_extern_fn!(mouse_entered, MouseEvent::CursorEntered);
|
||||||
|
mouse_simple_extern_fn!(mouse_exited, MouseEvent::CursorLeft);
|
|
@ -1,33 +1,52 @@
|
||||||
|
/// macOS window handling
|
||||||
|
///
|
||||||
|
/// Inspired by implementation in https://github.com/antonok-edm/vst_window
|
||||||
|
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cocoa::appkit::{
|
use cocoa::appkit::{
|
||||||
NSApp, NSApplication, NSApplicationActivateIgnoringOtherApps,
|
NSApp, NSApplication, NSApplicationActivationPolicyRegular,
|
||||||
NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, NSRunningApplication, NSView,
|
NSBackingStoreBuffered, NSWindow, NSWindowStyleMask,
|
||||||
NSWindow, NSWindowStyleMask,
|
|
||||||
};
|
};
|
||||||
use cocoa::base::{id, nil, NO};
|
use cocoa::base::{id, nil, NO};
|
||||||
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
|
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
|
||||||
|
|
||||||
|
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
||||||
|
|
||||||
use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle};
|
use raw_window_handle::{macos::MacOSHandle, HasRawWindowHandle, RawWindowHandle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Event, KeyboardEvent, MouseButton, MouseEvent, ScrollDelta, WindowEvent, WindowHandler,
|
Event, Parent, WindowHandler, WindowOpenOptions, WindowScalePolicy,
|
||||||
WindowOpenOptions, WindowScalePolicy, WindowInfo,
|
WindowInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::view::create_view;
|
||||||
|
|
||||||
|
|
||||||
|
/// Name of the field used to store the `WindowState` pointer in the custom
|
||||||
|
/// view class.
|
||||||
|
pub(super) const WINDOW_STATE_IVAR_NAME: &str = "WINDOW_STATE_IVAR_NAME";
|
||||||
|
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
ns_window: id,
|
/// Only set if we created the parent window, i.e. we are running in
|
||||||
|
/// parentless mode
|
||||||
|
ns_window: Option<id>,
|
||||||
|
/// Our subclassed NSView
|
||||||
ns_view: id,
|
ns_view: id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub struct WindowHandle;
|
pub struct WindowHandle;
|
||||||
|
|
||||||
|
|
||||||
impl WindowHandle {
|
impl WindowHandle {
|
||||||
pub fn app_run_blocking(self) {
|
pub fn app_run_blocking(self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let app = NSApp();
|
// Get reference to already created shared NSApplication object
|
||||||
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
// and run the main loop
|
||||||
app.run();
|
NSApp().run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,62 +57,162 @@ impl Window {
|
||||||
B: FnOnce(&mut Window) -> H,
|
B: FnOnce(&mut Window) -> H,
|
||||||
B: Send + 'static
|
B: Send + 'static
|
||||||
{
|
{
|
||||||
|
let _pool = unsafe { NSAutoreleasePool::new(nil) };
|
||||||
|
|
||||||
|
let mut window = match options.parent {
|
||||||
|
Parent::WithParent(parent) => {
|
||||||
|
if let RawWindowHandle::MacOS(handle) = parent {
|
||||||
|
let ns_view = handle.ns_view as *mut objc::runtime::Object;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let subview = create_view::<H>(&options);
|
||||||
|
|
||||||
|
let _: id = msg_send![ns_view, addSubview: subview];
|
||||||
|
|
||||||
|
Window {
|
||||||
|
ns_window: None,
|
||||||
|
ns_view: subview,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("Not a macOS window");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Parent::AsIfParented => {
|
||||||
|
let ns_view = unsafe {
|
||||||
|
create_view::<H>(&options)
|
||||||
|
};
|
||||||
|
|
||||||
|
Window {
|
||||||
|
ns_window: None,
|
||||||
|
ns_view,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Parent::None => {
|
||||||
|
// It seems prudent to run NSApp() here before doing other
|
||||||
|
// work. It runs [NSApplication sharedApplication], which is
|
||||||
|
// what is run at the very start of the Xcode-generated main
|
||||||
|
// function of a cocoa app according to:
|
||||||
|
// https://developer.apple.com/documentation/appkit/nsapplication
|
||||||
|
unsafe {
|
||||||
|
let app = NSApp();
|
||||||
|
app.setActivationPolicy_(
|
||||||
|
NSApplicationActivationPolicyRegular
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let scaling = match options.scale {
|
||||||
|
WindowScalePolicy::ScaleFactor(scale) => scale,
|
||||||
|
WindowScalePolicy::SystemScaleFactor => {
|
||||||
|
get_scaling().unwrap_or(1.0)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_info = WindowInfo::from_logical_size(
|
||||||
|
options.size,
|
||||||
|
scaling
|
||||||
|
);
|
||||||
|
|
||||||
|
let rect = NSRect::new(
|
||||||
|
NSPoint::new(0.0, 0.0),
|
||||||
|
NSSize::new(
|
||||||
|
window_info.logical_size().width as f64,
|
||||||
|
window_info.logical_size().height as f64
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let ns_window = NSWindow::alloc(nil)
|
||||||
|
.initWithContentRect_styleMask_backing_defer_(
|
||||||
|
rect,
|
||||||
|
NSWindowStyleMask::NSTitledWindowMask,
|
||||||
|
NSBackingStoreBuffered,
|
||||||
|
NO,
|
||||||
|
)
|
||||||
|
.autorelease();
|
||||||
|
ns_window.center();
|
||||||
|
|
||||||
|
let title = NSString::alloc(nil)
|
||||||
|
.init_str(&options.title)
|
||||||
|
.autorelease();
|
||||||
|
ns_window.setTitle_(title);
|
||||||
|
|
||||||
|
ns_window.makeKeyAndOrderFront_(nil);
|
||||||
|
|
||||||
|
let subview = create_view::<H>(&options);
|
||||||
|
|
||||||
|
ns_window.setContentView_(subview);
|
||||||
|
|
||||||
|
Window {
|
||||||
|
ns_window: Some(ns_window),
|
||||||
|
ns_view: subview,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let window_handler = build(&mut window);
|
||||||
|
|
||||||
|
let window_state_arc = Arc::new(WindowState {
|
||||||
|
window,
|
||||||
|
window_handler,
|
||||||
|
});
|
||||||
|
|
||||||
|
let window_state_pointer = Arc::into_raw(
|
||||||
|
window_state_arc.clone()
|
||||||
|
) as *mut c_void;
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _pool = NSAutoreleasePool::new(nil);
|
(*window_state_arc.window.ns_view).set_ivar(
|
||||||
|
WINDOW_STATE_IVAR_NAME,
|
||||||
let scaling = match options.scale {
|
window_state_pointer
|
||||||
WindowScalePolicy::SystemScaleFactor => get_scaling().unwrap_or(1.0),
|
|
||||||
WindowScalePolicy::ScaleFactor(scale) => scale
|
|
||||||
};
|
|
||||||
|
|
||||||
let window_info = WindowInfo::from_logical_size(options.size, scaling);
|
|
||||||
|
|
||||||
let rect = NSRect::new(
|
|
||||||
NSPoint::new(0.0, 0.0),
|
|
||||||
NSSize::new(
|
|
||||||
window_info.logical_size().width as f64,
|
|
||||||
window_info.logical_size().height as f64
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let ns_window = NSWindow::alloc(nil)
|
|
||||||
.initWithContentRect_styleMask_backing_defer_(
|
|
||||||
rect,
|
|
||||||
NSWindowStyleMask::NSTitledWindowMask,
|
|
||||||
NSBackingStoreBuffered,
|
|
||||||
NO,
|
|
||||||
)
|
|
||||||
.autorelease();
|
|
||||||
ns_window.center();
|
|
||||||
ns_window.setTitle_(NSString::alloc(nil).init_str(&options.title));
|
|
||||||
ns_window.makeKeyAndOrderFront_(nil);
|
|
||||||
|
|
||||||
let ns_view = NSView::alloc(nil).init();
|
|
||||||
ns_window.setContentView_(ns_view);
|
|
||||||
|
|
||||||
let mut window = Window { ns_window, ns_view };
|
|
||||||
|
|
||||||
let handler = build(&mut window);
|
|
||||||
|
|
||||||
// FIXME: only do this in the unparented case
|
|
||||||
let current_app = NSRunningApplication::currentApplication(nil);
|
|
||||||
current_app.activateWithOptions_(NSApplicationActivateIgnoringOtherApps);
|
|
||||||
|
|
||||||
WindowHandle
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WindowHandle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub(super) struct WindowState<H: WindowHandler> {
|
||||||
|
window: Window,
|
||||||
|
window_handler: H,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl <H: WindowHandler>WindowState<H> {
|
||||||
|
/// Returns a mutable reference to a WindowState from an Objective-C field
|
||||||
|
///
|
||||||
|
/// Don't use this to create two simulataneous references to a single
|
||||||
|
/// WindowState. Apparently, macOS blocks for the duration of an event,
|
||||||
|
/// callback, meaning that this shouldn't be a problem in practice.
|
||||||
|
pub(super) unsafe fn from_field(obj: &Object) -> &mut Self {
|
||||||
|
let state_ptr: *mut c_void = *obj.get_ivar(WINDOW_STATE_IVAR_NAME);
|
||||||
|
|
||||||
|
&mut *(state_ptr as *mut Self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn trigger_event(&mut self, event: Event){
|
||||||
|
self.window_handler.on_event(&mut self.window, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
unsafe impl HasRawWindowHandle for Window {
|
unsafe impl HasRawWindowHandle for Window {
|
||||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||||
|
let ns_window = self.ns_window.unwrap_or(
|
||||||
|
::std::ptr::null_mut()
|
||||||
|
) as *mut c_void;
|
||||||
|
|
||||||
RawWindowHandle::MacOS(MacOSHandle {
|
RawWindowHandle::MacOS(MacOSHandle {
|
||||||
ns_window: self.ns_window as *mut c_void,
|
ns_window,
|
||||||
ns_view: self.ns_view as *mut c_void,
|
ns_view: self.ns_view as *mut c_void,
|
||||||
..MacOSHandle::empty()
|
..MacOSHandle::empty()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn get_scaling() -> Option<f64> {
|
fn get_scaling() -> Option<f64> {
|
||||||
// TODO: find system scaling
|
// TODO: find system scaling
|
||||||
None
|
None
|
||||||
|
|
Loading…
Reference in a new issue