diff --git a/bindings/c/src/macos.rs b/bindings/c/src/macos.rs index fe953f563..7d6fa6e8a 100644 --- a/bindings/c/src/macos.rs +++ b/bindings/c/src/macos.rs @@ -128,6 +128,24 @@ impl macos_subclassing_adapter { BoxCastPtr::to_mut_ptr(adapter) } + /// This function takes ownership of `handler`. + #[no_mangle] + pub unsafe extern "C" fn accesskit_macos_subclassing_adapter_for_window( + window: *mut c_void, + source: tree_update_factory, + source_userdata: *mut c_void, + handler: *mut action_handler, + ) -> *mut macos_subclassing_adapter { + let source = source.unwrap(); + let handler = box_from_ptr(handler); + let adapter = SubclassingAdapter::for_window( + window, + move || box_from_ptr(source(source_userdata)).into(), + handler, + ); + BoxCastPtr::to_mut_ptr(adapter) + } + #[no_mangle] pub extern "C" fn accesskit_macos_subclassing_adapter_free( adapter: *mut macos_subclassing_adapter, diff --git a/platforms/macos/src/appkit/window.rs b/platforms/macos/src/appkit/window.rs index 35407626b..a58712ace 100644 --- a/platforms/macos/src/appkit/window.rs +++ b/platforms/macos/src/appkit/window.rs @@ -6,10 +6,12 @@ use objc2::{ extern_class, extern_methods, foundation::{NSObject, NSPoint, NSRect}, + msg_send_id, + rc::{Id, Shared}, ClassType, }; -use super::NSResponder; +use super::{NSResponder, NSView}; extern_class!( #[derive(Debug)] @@ -28,5 +30,9 @@ extern_methods!( #[sel(convertPointFromScreen:)] pub(crate) fn convert_point_from_screen(&self, point: NSPoint) -> NSPoint; + + pub(crate) fn content_view(&self) -> Option> { + unsafe { msg_send_id![self, contentView] } + } } ); diff --git a/platforms/macos/src/subclass.rs b/platforms/macos/src/subclass.rs index d137009db..744f78ea0 100644 --- a/platforms/macos/src/subclass.rs +++ b/platforms/macos/src/subclass.rs @@ -20,7 +20,11 @@ use objc2::{ use once_cell::{sync::Lazy as SyncLazy, unsync::Lazy}; use std::{collections::HashMap, ffi::c_void, sync::Mutex}; -use crate::{appkit::NSView, event::QueuedEvents, Adapter}; +use crate::{ + appkit::{NSView, NSWindow}, + event::QueuedEvents, + Adapter, +}; static SUBCLASSES: SyncLazy>> = SyncLazy::new(|| Mutex::new(HashMap::new())); @@ -116,6 +120,14 @@ impl SubclassingAdapter { ) -> Self { let view = view as *mut NSView; let retained_view = unsafe { Id::retain(view) }.unwrap(); + Self::new_internal(retained_view, source, action_handler) + } + + fn new_internal( + retained_view: Id, + source: impl 'static + FnOnce() -> TreeUpdate, + action_handler: Box, + ) -> Self { let adapter: LazyAdapter = { let retained_view = retained_view.clone(); Lazy::new(Box::new(move || { @@ -123,6 +135,7 @@ impl SubclassingAdapter { unsafe { Adapter::new(view, source(), action_handler) } })) }; + let view = Id::as_ptr(&retained_view) as *mut NSView; // Cast to a pointer and back to force the lifetime to 'static // SAFETY: We know the class will live as long as the instance, // and we only use this reference while the instance is alive. @@ -171,6 +184,29 @@ impl SubclassingAdapter { } } + /// Create an adapter that dynamically subclasses the content view + /// of the specified window. + /// + /// The action handler will always be called on the main thread. + /// + /// # Safety + /// + /// `window` must be a valid, unreleased pointer to an `NSWindow`. + /// + /// # Panics + /// + /// This function panics if the specified window doesn't currently have + /// a content view. + pub unsafe fn for_window( + window: *mut c_void, + source: impl 'static + FnOnce() -> TreeUpdate, + action_handler: Box, + ) -> Self { + let window = unsafe { &*(window as *const NSWindow) }; + let retained_view = window.content_view().unwrap(); + Self::new_internal(retained_view, source, action_handler) + } + /// Initialize the tree if it hasn't been initialized already, then apply /// the provided update. ///