use accesskit::{ActionHandler, ActivationHandler, TreeUpdate};
use objc2::{
declare::ClassBuilder,
declare_class,
ffi::{
objc_getAssociatedObject, objc_setAssociatedObject, object_setClass,
OBJC_ASSOCIATION_RETAIN_NONATOMIC,
},
msg_send_id,
mutability::InteriorMutable,
rc::Id,
runtime::{AnyClass, Sel},
sel, ClassType, DeclaredClass,
};
use objc2_app_kit::{NSView, NSWindow};
use objc2_foundation::{NSArray, NSObject, NSPoint};
use std::{cell::RefCell, ffi::c_void, sync::Mutex};
use crate::{event::QueuedEvents, Adapter};
static SUBCLASSES: Mutex<Vec<(&'static AnyClass, &'static AnyClass)>> = Mutex::new(Vec::new());
static ASSOCIATED_OBJECT_KEY: u8 = 0;
fn associated_object_key() -> *const c_void {
(&ASSOCIATED_OBJECT_KEY as *const u8).cast()
}
struct AssociatedObjectState {
adapter: Adapter,
activation_handler: Box<dyn ActivationHandler>,
}
struct AssociatedObjectIvars {
state: RefCell<AssociatedObjectState>,
prev_class: &'static AnyClass,
}
declare_class!(
struct AssociatedObject;
unsafe impl ClassType for AssociatedObject {
type Super = NSObject;
type Mutability = InteriorMutable;
const NAME: &'static str = "AccessKitSubclassAssociatedObject";
}
impl DeclaredClass for AssociatedObject {
type Ivars = AssociatedObjectIvars;
}
);
impl AssociatedObject {
fn new(
adapter: Adapter,
activation_handler: impl 'static + ActivationHandler,
prev_class: &'static AnyClass,
) -> Id<Self> {
let state = RefCell::new(AssociatedObjectState {
adapter,
activation_handler: Box::new(activation_handler),
});
let this = Self::alloc().set_ivars(AssociatedObjectIvars { state, prev_class });
unsafe { msg_send_id![super(this), init] }
}
}
fn associated_object(view: &NSView) -> &AssociatedObject {
unsafe {
(objc_getAssociatedObject(view as *const NSView as *const _, associated_object_key())
as *const AssociatedObject)
.as_ref()
}
.unwrap()
}
unsafe extern "C" fn superclass(this: &NSView, _cmd: Sel) -> Option<&AnyClass> {
let associated = associated_object(this);
associated.ivars().prev_class.superclass()
}
unsafe extern "C" fn children(this: &NSView, _cmd: Sel) -> *mut NSArray<NSObject> {
let associated = associated_object(this);
let mut state = associated.ivars().state.borrow_mut();
let state_mut = &mut *state;
state_mut
.adapter
.view_children(&mut *state_mut.activation_handler)
}
unsafe extern "C" fn focus(this: &NSView, _cmd: Sel) -> *mut NSObject {
let associated = associated_object(this);
let mut state = associated.ivars().state.borrow_mut();
let state_mut = &mut *state;
state_mut.adapter.focus(&mut *state_mut.activation_handler)
}
unsafe extern "C" fn hit_test(this: &NSView, _cmd: Sel, point: NSPoint) -> *mut NSObject {
let associated = associated_object(this);
let mut state = associated.ivars().state.borrow_mut();
let state_mut = &mut *state;
state_mut
.adapter
.hit_test(point, &mut *state_mut.activation_handler)
}
pub struct SubclassingAdapter {
view: Id<NSView>,
associated: Id<AssociatedObject>,
}
impl SubclassingAdapter {
pub unsafe fn new(
view: *mut c_void,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler,
) -> Self {
let view = view as *mut NSView;
let retained_view = unsafe { Id::retain(view) }.unwrap();
Self::new_internal(retained_view, activation_handler, action_handler)
}
fn new_internal(
retained_view: Id<NSView>,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler,
) -> Self {
let view = Id::as_ptr(&retained_view) as *mut NSView;
let adapter = unsafe { Adapter::new(view as *mut c_void, false, action_handler) };
let prev_class = unsafe { &*((*view).class() as *const AnyClass) };
let associated = AssociatedObject::new(adapter, activation_handler, prev_class);
unsafe {
objc_setAssociatedObject(
view as *mut _,
associated_object_key(),
Id::as_ptr(&associated) as *mut _,
OBJC_ASSOCIATION_RETAIN_NONATOMIC,
)
};
let mut subclasses = SUBCLASSES.lock().unwrap();
let subclass = match subclasses.iter().find(|entry| entry.0 == prev_class) {
Some(entry) => entry.1,
None => {
let name = format!("AccessKitSubclassOf{}", prev_class.name());
let mut builder = ClassBuilder::new(&name, prev_class).unwrap();
unsafe {
builder.add_method(
sel!(superclass),
superclass as unsafe extern "C" fn(_, _) -> _,
);
builder.add_method(
sel!(accessibilityChildren),
children as unsafe extern "C" fn(_, _) -> _,
);
builder.add_method(
sel!(accessibilityFocusedUIElement),
focus as unsafe extern "C" fn(_, _) -> _,
);
builder.add_method(
sel!(accessibilityHitTest:),
hit_test as unsafe extern "C" fn(_, _, _) -> _,
);
}
let class = builder.register();
subclasses.push((prev_class, class));
class
}
};
unsafe { object_setClass(view as *mut _, (subclass as *const AnyClass).cast()) };
Self {
view: retained_view,
associated,
}
}
pub unsafe fn for_window(
window: *mut c_void,
activation_handler: impl 'static + ActivationHandler,
action_handler: impl 'static + ActionHandler,
) -> Self {
let window = unsafe { &*(window as *const NSWindow) };
let retained_view = window.contentView().unwrap();
Self::new_internal(retained_view, activation_handler, action_handler)
}
pub fn update_if_active(
&mut self,
update_factory: impl FnOnce() -> TreeUpdate,
) -> Option<QueuedEvents> {
let mut state = self.associated.ivars().state.borrow_mut();
state.adapter.update_if_active(update_factory)
}
pub fn update_view_focus_state(&mut self, is_focused: bool) -> Option<QueuedEvents> {
let mut state = self.associated.ivars().state.borrow_mut();
state.adapter.update_view_focus_state(is_focused)
}
}
impl Drop for SubclassingAdapter {
fn drop(&mut self) {
let prev_class = self.associated.ivars().prev_class;
let view = Id::as_ptr(&self.view) as *mut NSView;
unsafe { object_setClass(view as *mut _, (prev_class as *const AnyClass).cast()) };
unsafe {
objc_setAssociatedObject(
view as *mut _,
associated_object_key(),
std::ptr::null_mut(),
OBJC_ASSOCIATION_RETAIN_NONATOMIC,
)
};
}
}