131 lines
4.8 KiB
Rust
131 lines
4.8 KiB
Rust
|
//! 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<Object>,
|
||
|
pub objc_delegate: Id<Object>,
|
||
|
pub delegate: Box<dyn ToolbarDelegate>
|
||
|
}
|
||
|
|
||
|
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<D: ToolbarDelegate + 'static>(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::<D>();
|
||
|
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<D: ToolbarDelegate>(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::<Vec<id>>();
|
||
|
|
||
|
NSArray::arrayWithObjects(nil, &identifiers)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Loops back to the delegate.
|
||
|
extern fn default_item_identifiers<D: ToolbarDelegate>(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::<Vec<id>>();
|
||
|
|
||
|
NSArray::arrayWithObjects(nil, &identifiers)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Loops back to the delegate.
|
||
|
extern fn item_for_identifier<D: ToolbarDelegate>(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<D: ToolbarDelegate>() -> *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::<usize>(TOOLBAR_PTR);
|
||
|
|
||
|
// Add callback methods
|
||
|
decl.add_method(sel!(toolbarAllowedItemIdentifiers:), allowed_item_identifiers::<D> as extern fn(&Object, _, _) -> id);
|
||
|
decl.add_method(sel!(toolbarDefaultItemIdentifiers:), default_item_identifiers::<D> as extern fn(&Object, _, _) -> id);
|
||
|
decl.add_method(sel!(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:), item_for_identifier::<D> as extern fn(&Object, _, _, _, _) -> id);
|
||
|
|
||
|
TOOLBAR_DELEGATE_CLASS = decl.register();
|
||
|
});
|
||
|
|
||
|
unsafe { TOOLBAR_DELEGATE_CLASS }
|
||
|
}
|