Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for Window::Focused/Unfocused events on macOS #171

Merged
merged 7 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/macos/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ use super::{
/// Name of the field used to store the `WindowState` pointer.
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 {
($class:ident, $sel:ident, $event:expr) => {
#[allow(non_snake_case)]
Expand Down Expand Up @@ -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 {
let class = create_view_class();

Expand All @@ -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)));

register_notification(view, NSWindowDidBecomeKeyNotification, nil);
register_notification(view, NSWindowDidResignKeyNotification, nil);

let _: id = msg_send![
view,
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
Expand All @@ -124,6 +145,14 @@ unsafe fn create_view_class() -> &'static Class {
sel!(acceptsFirstResponder),
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!(preservesContentInLiveResize),
Expand Down Expand Up @@ -177,6 +206,10 @@ unsafe fn create_view_class() -> &'static Class {
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!(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, mouseUp, ButtonReleased, MouseButton::Left);
Expand Down Expand Up @@ -208,6 +241,25 @@ extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL
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 {
let state = unsafe { WindowState::from_view(this) };

Expand Down Expand Up @@ -473,3 +525,30 @@ extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) {

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
}));
}
}
}
8 changes: 6 additions & 2 deletions src/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ use core_foundation::runloop::{
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
};
use keyboard_types::KeyboardEvent;

use objc::class;
use objc::{msg_send, runtime::Object, sel, sel_impl};

use raw_window_handle::{
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
RawDisplayHandle, RawWindowHandle,
Expand Down Expand Up @@ -83,6 +82,11 @@ impl WindowInner {
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);

// Close the window if in non-parented mode
Expand Down
Loading