//! Implements an NSToolbar, which is one of those macOS niceties //! that makes it feel... "proper". //! //! UNFORTUNATELY, this is a very old and janky API. So... yeah. use std::sync::Once; use cocoa::base::{id, nil}; use cocoa::foundation::{NSArray, NSString}; use objc_id::Id; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{msg_send, sel, sel_impl}; use crate::toolbar::item::ToolbarItem; use crate::utils::str_from; static TOOLBAR_PTR: &str = "rstToolbarPtr"; /// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`. pub trait ToolbarDelegate { /// What items are allowed in this toolbar. fn allowed_item_identifiers(&self) -> Vec<&'static str>; /// The default items in this toolbar. fn default_item_identifiers(&self) -> Vec<&'static str>; /// For a given `identifier`, return the `ToolbarItem` that should be displayed. fn item_for(&self, _identifier: &str) -> ToolbarItem; } /// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime /// where our `NSWindow` and associated delegate live. pub struct Toolbar { pub inner: Id, pub objc_delegate: Id, pub delegate: Box } impl Toolbar { /// Creates a new `NSToolbar` instance, configures it appropriately, injects an `NSObject` /// delegate wrapper, and retains the necessary Objective-C runtime pointers. pub fn new(identifier: &str, delegate: D) -> Self { let inner = unsafe { let identifier = NSString::alloc(nil).init_str(identifier); let alloc: id = msg_send![Class::get("NSToolbar").unwrap(), alloc]; let toolbar: id = msg_send![alloc, initWithIdentifier:identifier]; Id::from_ptr(toolbar) }; let toolbar_delegate = Box::new(delegate); let objc_delegate = unsafe { let delegate_class = register_delegate_class::(); let objc_delegate: id = msg_send![delegate_class, new]; let delegate_ptr: *const D = &*toolbar_delegate; (&mut *objc_delegate).set_ivar(TOOLBAR_PTR, delegate_ptr as usize); let _: () = msg_send![&*inner, setDelegate:objc_delegate]; Id::from_ptr(objc_delegate) }; Toolbar { inner: inner, objc_delegate: objc_delegate, delegate: toolbar_delegate } } } /// Loops back to the delegate. extern fn allowed_item_identifiers(this: &Object, _: Sel, _: id) -> id { unsafe { let ptr: usize = *this.get_ivar(TOOLBAR_PTR); let toolbar = ptr as *mut D; let identifiers = (*toolbar).allowed_item_identifiers().iter().map(|identifier| { NSString::alloc(nil).init_str(identifier) }).collect::>(); NSArray::arrayWithObjects(nil, &identifiers) } } /// Loops back to the delegate. extern fn default_item_identifiers(this: &Object, _: Sel, _: id) -> id { unsafe { let ptr: usize = *this.get_ivar(TOOLBAR_PTR); let toolbar = ptr as *mut D; let identifiers = (*toolbar).default_item_identifiers().iter().map(|identifier| { NSString::alloc(nil).init_str(identifier) }).collect::>(); NSArray::arrayWithObjects(nil, &identifiers) } } /// Loops back to the delegate. extern fn item_for_identifier(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id { unsafe { let ptr: usize = *this.get_ivar(TOOLBAR_PTR); let toolbar = ptr as *mut D; let identifier = str_from(identifier); let mut item = (*toolbar).item_for(identifier); &mut *item.inner } } /// Registers an `NSObject` subclass, and configures it to hold some ivars for various things we need /// to store. fn register_delegate_class() -> *const Class { static mut TOOLBAR_DELEGATE_CLASS: *const Class = 0 as *const Class; static INIT: Once = Once::new(); INIT.call_once(|| unsafe { let superclass = Class::get("NSObject").unwrap(); let mut decl = ClassDecl::new("RSTToolbarDelegate", superclass).unwrap(); // For callbacks decl.add_ivar::(TOOLBAR_PTR); // Add callback methods decl.add_method(sel!(toolbarAllowedItemIdentifiers:), allowed_item_identifiers:: as extern fn(&Object, _, _) -> id); decl.add_method(sel!(toolbarDefaultItemIdentifiers:), default_item_identifiers:: as extern fn(&Object, _, _) -> id); decl.add_method(sel!(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:), item_for_identifier:: as extern fn(&Object, _, _, _, _) -> id); TOOLBAR_DELEGATE_CLASS = decl.register(); }); unsafe { TOOLBAR_DELEGATE_CLASS } }