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 4 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
27 changes: 27 additions & 0 deletions src/macos/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,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 @@ -208,6 +216,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![state.window_inner.ns_view, 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
112 changes: 104 additions & 8 deletions src/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,28 @@ use cocoa::appkit::{
NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered,
NSPasteboard, NSView, NSWindow, NSWindowStyleMask,
};
use cocoa::base::{id, nil, NO, YES};
use cocoa::base::{id, nil, BOOL, NO, YES};
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
use core_foundation::runloop::{
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
};
use keyboard_types::KeyboardEvent;

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

use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Object, Sel},
sel, sel_impl,
};
use raw_window_handle::{
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
RawDisplayHandle, RawWindowHandle,
};
use uuid::Uuid;

use crate::{
Event, EventStatus, MouseCursor, Size, WindowHandler, WindowInfo, WindowOpenOptions,
WindowScalePolicy,
Event, EventStatus, MouseCursor, Size, WindowEvent, WindowHandler, WindowInfo,
WindowOpenOptions, WindowScalePolicy,
};

use super::keyboard::KeyboardState;
Expand All @@ -34,6 +39,7 @@ use crate::gl::{GlConfig, GlContext};

pub struct WindowHandle {
state: Rc<WindowState>,
_observer: NSWindowNotificationObserver,
httnn marked this conversation as resolved.
Show resolved Hide resolved
}

impl WindowHandle {
Expand Down Expand Up @@ -62,7 +68,7 @@ pub(super) struct WindowInner {
/// parentless mode
ns_window: Cell<Option<id>>,
/// Our subclassed NSView
ns_view: id,
pub(crate) ns_view: id,

#[cfg(feature = "opengl")]
gl_context: Option<GlContext>,
Expand Down Expand Up @@ -273,7 +279,10 @@ impl<'a> Window<'a> {
WindowState::setup_timer(window_state_ptr);
}

WindowHandle { state: window_state }
WindowHandle {
_observer: NSWindowNotificationObserver::new(window_state.clone()),
state: window_state,
}
}

pub fn close(&mut self) {
Expand Down Expand Up @@ -415,3 +424,90 @@ pub fn copy_to_clipboard(string: &str) {
pb.setString_forType(ns_str, cocoa::appkit::NSPasteboardTypeString);
}
}

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![state.window_inner.ns_view, 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 == state.window_inner.ns_view {
let is_key_window: BOOL = msg_send![window, isKeyWindow];
state.trigger_event(Event::Window(if is_key_window == YES {
WindowEvent::Focused
} else {
WindowEvent::Unfocused
}));
}
}
}

struct NSWindowNotificationObserver {
observer: id,
}

impl NSWindowNotificationObserver {
const NS_WINDOW_DID_BECOME_KEY_NOTIFICATION: &'static str = "NSWindowDidBecomeKeyNotification";
const NS_WINDOW_DID_RESIGN_KEY_NOTIFICATION: &'static str = "NSWindowDidResignKeyNotification";

fn new(window_state: Rc<WindowState>) -> Self {
unsafe {
// Create unique observer class and instantiate it.
let observer: id = {
let name = format!("BaseviewObserver_{}", Uuid::new_v4().to_simple());
httnn marked this conversation as resolved.
Show resolved Hide resolved
let mut decl = ClassDecl::new(&name, class!(NSObject)).unwrap();
decl.add_method(
sel!(handleNotification:),
handle_notification as extern "C" fn(&Object, Sel, id),
);
decl.add_ivar::<*mut c_void>(BASEVIEW_STATE_IVAR);
let class = decl.register();
msg_send![class, new]
};

// Attach a pointer to WindowState to the observer instance.
let window_state_ptr = Rc::into_raw(Rc::clone(&window_state));
httnn marked this conversation as resolved.
Show resolved Hide resolved
(*observer).set_ivar(BASEVIEW_STATE_IVAR, window_state_ptr as *const c_void);

// Start observing notifications for when NSWindows become/resign key status.
let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter];

let _: () = msg_send![
notification_center,
addObserver:observer
selector:sel!(handleNotification:)
name:NSString::alloc(nil).init_str(Self::NS_WINDOW_DID_BECOME_KEY_NOTIFICATION)
object:nil
];

let _: () = msg_send![
notification_center,
addObserver:observer
selector:sel!(handleNotification:)
name:NSString::alloc(nil).init_str(Self::NS_WINDOW_DID_RESIGN_KEY_NOTIFICATION)
object:nil
];

Self { observer }
}
}
}

impl Drop for NSWindowNotificationObserver {
fn drop(&mut self) {
unsafe {
let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter];
let _: () = msg_send![notification_center, removeObserver:self.observer];
}
}
}
Loading