Clean up macOS class declaration (#2458)

* Begin abstraction over AppKit

* Clean up NSApplication delegate declaration

* Clean up NSApplication override declaration

* Clean up NSWindow delegate declaration

* Clean up NSWindow override declaration

* Clean up NSView delegate declaration
This commit is contained in:
Mads Marquart 2022-09-02 18:46:18 +02:00 committed by GitHub
parent 112965b4ff
commit d67c928120
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 873 additions and 1013 deletions

View file

@ -1,89 +1,64 @@
use std::{
cell::{RefCell, RefMut},
os::raw::c_void,
};
use cocoa::appkit::NSApplicationActivationPolicy;
use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{declare_class, ClassType};
use cocoa::base::id;
use objc::{
declare::ClassBuilder,
runtime::{Class, Object, Sel},
};
use once_cell::sync::Lazy;
use super::app_state::AppState;
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
declare_class!(
#[derive(Debug)]
pub(super) struct ApplicationDelegate {
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
}
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
unsafe impl ClassType for ApplicationDelegate {
type Super = NSObject;
const NAME: &'static str = "WinitApplicationDelegate";
}
pub struct AuxDelegateState {
pub activation_policy: ActivationPolicy,
pub default_menu: bool,
}
unsafe impl ApplicationDelegate {
#[sel(initWithActivationPolicy:defaultMenu:)]
fn init(
&mut self,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
) -> Option<&mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
*this.activation_policy = activation_policy;
*this.default_menu = default_menu;
this
})
}
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
unsafe impl Sync for AppDelegateClass {}
#[sel(applicationDidFinishLaunching:)]
fn did_finish_launching(&self, _sender: *const Object) {
trace_scope!("applicationDidFinishLaunching:");
AppState::launched(*self.activation_policy, *self.default_menu);
}
pub static APP_DELEGATE_CLASS: Lazy<AppDelegateClass> = Lazy::new(|| unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassBuilder::new("WinitAppDelegate", superclass).unwrap();
#[sel(applicationWillTerminate:)]
fn will_terminate(&self, _sender: *const Object) {
trace_scope!("applicationWillTerminate:");
// TODO: Notify every window that it will be destroyed, like done in iOS?
AppState::exit();
}
}
);
decl.add_class_method(sel!(new), new as extern "C" fn(_, _) -> _);
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(_, _));
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(_, _, _),
);
decl.add_method(
sel!(applicationWillTerminate:),
will_terminate as extern "C" fn(_, _, _),
);
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
AppDelegateClass(decl.register())
});
/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
let ptr: *mut c_void = *this.ivar(AUX_DELEGATE_STATE_NAME);
// Watch out that this needs to be the correct type
(*(ptr as *mut RefCell<AuxDelegateState>)).borrow_mut()
}
extern "C" fn new(class: &Class, _: Sel) -> id {
unsafe {
let this: id = msg_send![class, alloc];
let this: id = msg_send![this, init];
// TODO: Remove the need for this initialization here
(*this).set_ivar(
AUX_DELEGATE_STATE_NAME,
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
activation_policy: ActivationPolicy::Regular,
default_menu: true,
}))) as *mut c_void,
);
this
impl ApplicationDelegate {
pub(super) fn new(
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
) -> Id<Self, Shared> {
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithActivationPolicy: activation_policy,
defaultMenu: default_menu,
]
}
}
}
extern "C" fn dealloc(this: &Object, _: Sel) {
unsafe {
let state_ptr: *mut c_void = *(this.ivar(AUX_DELEGATE_STATE_NAME));
// As soon as the box is constructed it is immediately dropped, releasing the underlying
// memory
drop(Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>));
}
}
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
trace_scope!("applicationDidFinishLaunching:");
AppState::launched(this);
}
extern "C" fn will_terminate(_this: &Object, _: Sel, _: id) {
trace!("Triggered `applicationWillTerminate`");
// TODO: Notify every window that it will be destroyed, like done in iOS?
AppState::exit();
trace!("Completed `applicationWillTerminate`");
}