add support for Window::Focused/Unfocused events on macOS (#171)
trigger `WindowEvent::Focused` and `WindowEvent::Unfocused` events when the plugin window gains/loses focus. implemented by adding observers to `NSNotificationCenter::defaultCenter()` that listen to `NSWindowDidBecomeKeyNotification` and `NSWindowDidResignKeyNotification` notifications on the `NSViews`' window. tested and confirmed to work in Live, Bitwig, FL Studio, Reaper and AudioPluginHost.
This commit is contained in:
parent
fdc5d282fc
commit
085ae2a27e
|
@ -29,6 +29,12 @@ use super::{
|
||||||
/// Name of the field used to store the `WindowState` pointer.
|
/// Name of the field used to store the `WindowState` pointer.
|
||||||
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
|
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
|
||||||
|
|
||||||
|
#[link(name = "AppKit", kind = "framework")]
|
||||||
|
extern "C" {
|
||||||
|
static NSWindowDidBecomeKeyNotification: id;
|
||||||
|
static NSWindowDidResignKeyNotification: id;
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! add_simple_mouse_class_method {
|
macro_rules! add_simple_mouse_class_method {
|
||||||
($class:ident, $sel:ident, $event:expr) => {
|
($class:ident, $sel:ident, $event:expr) => {
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -94,6 +100,18 @@ macro_rules! add_simple_keyboard_class_method {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe fn register_notification(observer: id, notification_name: id, object: id) {
|
||||||
|
let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter];
|
||||||
|
|
||||||
|
let _: () = msg_send![
|
||||||
|
notification_center,
|
||||||
|
addObserver:observer
|
||||||
|
selector:sel!(handleNotification:)
|
||||||
|
name:notification_name
|
||||||
|
object:object
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
|
pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
|
||||||
let class = create_view_class();
|
let class = create_view_class();
|
||||||
|
|
||||||
|
@ -103,6 +121,9 @@ pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
|
||||||
|
|
||||||
view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height)));
|
view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height)));
|
||||||
|
|
||||||
|
register_notification(view, NSWindowDidBecomeKeyNotification, nil);
|
||||||
|
register_notification(view, NSWindowDidResignKeyNotification, nil);
|
||||||
|
|
||||||
let _: id = msg_send![
|
let _: id = msg_send![
|
||||||
view,
|
view,
|
||||||
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
|
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
|
||||||
|
@ -124,6 +145,14 @@ unsafe fn create_view_class() -> &'static Class {
|
||||||
sel!(acceptsFirstResponder),
|
sel!(acceptsFirstResponder),
|
||||||
property_yes as extern "C" fn(&Object, Sel) -> BOOL,
|
property_yes as extern "C" fn(&Object, Sel) -> BOOL,
|
||||||
);
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(becomeFirstResponder),
|
||||||
|
become_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
|
||||||
|
);
|
||||||
|
class.add_method(
|
||||||
|
sel!(resignFirstResponder),
|
||||||
|
resign_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
|
||||||
|
);
|
||||||
class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL);
|
class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL);
|
||||||
class.add_method(
|
class.add_method(
|
||||||
sel!(preservesContentInLiveResize),
|
sel!(preservesContentInLiveResize),
|
||||||
|
@ -177,6 +206,10 @@ unsafe fn create_view_class() -> &'static Class {
|
||||||
dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger,
|
dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger,
|
||||||
);
|
);
|
||||||
class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id));
|
class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id));
|
||||||
|
class.add_method(
|
||||||
|
sel!(handleNotification:),
|
||||||
|
handle_notification as extern "C" fn(&Object, Sel, id),
|
||||||
|
);
|
||||||
|
|
||||||
add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left);
|
add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left);
|
||||||
add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left);
|
add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left);
|
||||||
|
@ -208,6 +241,25 @@ extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL
|
||||||
YES
|
YES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL {
|
||||||
|
let state = unsafe { WindowState::from_view(this) };
|
||||||
|
let is_key_window = unsafe {
|
||||||
|
let window: id = msg_send![this, window];
|
||||||
|
let is_key_window: BOOL = msg_send![window, isKeyWindow];
|
||||||
|
is_key_window == YES
|
||||||
|
};
|
||||||
|
if is_key_window {
|
||||||
|
state.trigger_event(Event::Window(WindowEvent::Focused));
|
||||||
|
}
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn resign_first_responder(this: &Object, _sel: Sel) -> BOOL {
|
||||||
|
let state = unsafe { WindowState::from_view(this) };
|
||||||
|
state.trigger_event(Event::Window(WindowEvent::Unfocused));
|
||||||
|
YES
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL {
|
extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL {
|
||||||
let state = unsafe { WindowState::from_view(this) };
|
let state = unsafe { WindowState::from_view(this) };
|
||||||
|
|
||||||
|
@ -473,3 +525,30 @@ extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) {
|
||||||
|
|
||||||
on_event(&state, MouseEvent::DragLeft);
|
on_event(&state, MouseEvent::DragLeft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn handle_notification(this: &Object, _cmd: Sel, notification: id) {
|
||||||
|
unsafe {
|
||||||
|
let state = WindowState::from_view(this);
|
||||||
|
|
||||||
|
// The subject of the notication, in this case an NSWindow object.
|
||||||
|
let notification_object: id = msg_send![notification, object];
|
||||||
|
|
||||||
|
// The NSWindow object associated with our NSView.
|
||||||
|
let window: id = msg_send![this, window];
|
||||||
|
|
||||||
|
let first_responder: id = msg_send![window, firstResponder];
|
||||||
|
|
||||||
|
// Only trigger focus events if the NSWindow that's being notified about is our window,
|
||||||
|
// and if the window's first responder is our NSView.
|
||||||
|
// If the first responder isn't our NSView, the focus events will instead be triggered
|
||||||
|
// by the becomeFirstResponder and resignFirstResponder methods on the NSView itself.
|
||||||
|
if notification_object == window && first_responder == this as *const Object as id {
|
||||||
|
let is_key_window: BOOL = msg_send![window, isKeyWindow];
|
||||||
|
state.trigger_event(Event::Window(if is_key_window == YES {
|
||||||
|
WindowEvent::Focused
|
||||||
|
} else {
|
||||||
|
WindowEvent::Unfocused
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,9 +13,8 @@ use core_foundation::runloop::{
|
||||||
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
|
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
|
||||||
};
|
};
|
||||||
use keyboard_types::KeyboardEvent;
|
use keyboard_types::KeyboardEvent;
|
||||||
|
use objc::class;
|
||||||
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
||||||
|
|
||||||
use raw_window_handle::{
|
use raw_window_handle::{
|
||||||
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
|
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
|
||||||
RawDisplayHandle, RawWindowHandle,
|
RawDisplayHandle, RawWindowHandle,
|
||||||
|
@ -83,6 +82,11 @@ impl WindowInner {
|
||||||
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
|
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deregister NSView from NotificationCenter.
|
||||||
|
let notification_center: id =
|
||||||
|
msg_send![class!(NSNotificationCenter), defaultCenter];
|
||||||
|
let () = msg_send![notification_center, removeObserver:self.ns_view];
|
||||||
|
|
||||||
drop(window_state);
|
drop(window_state);
|
||||||
|
|
||||||
// Close the window if in non-parented mode
|
// Close the window if in non-parented mode
|
||||||
|
|
Loading…
Reference in a new issue