Fix up the NSToolbar wrapper to follow the new paradigm

This commit is contained in:
Ryan McGrath 2020-03-11 19:45:35 -07:00
parent 8a5af12b47
commit db33c382b7
No known key found for this signature in database
GPG key ID: 811674B62B666830
9 changed files with 133 additions and 96 deletions

View file

@ -49,7 +49,7 @@ pub mod prelude {
pub use crate::menu::{Menu, MenuItem};
pub use crate::notifications::{Notification, NotificationCenter, NotificationAuthOption};
pub use crate::toolbar::{ToolbarDelegate};
pub use crate::toolbar::{Toolbar, ToolbarController};
pub use crate::networking::URLRequest;

View file

@ -12,7 +12,7 @@ use objc_id::Id;
use url::Url;
use crate::error::AppKitError;
use crate::pasteboard::types::{PasteboardName, PasteboardType};
use crate::pasteboard::types::{PasteboardName};
use crate::utils::str_from;
/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop.

View file

@ -14,21 +14,23 @@ use crate::button::Button;
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSWindow` and associated delegate live.
pub struct ToolbarItem<'a> {
pub identifier: &'a str,
pub struct ToolbarItem {
pub identifier: String,
pub inner: Id<Object>,
pub button: Option<Button>
}
impl<'a> ToolbarItem<'a> {
impl ToolbarItem {
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
/// pointers.
pub fn new(identifier: &'a str) -> Self {
pub fn new<S: Into<String>>(identifier: S) -> Self {
let identifier = identifier.into();
let inner = unsafe {
let identifier = NSString::alloc(nil).init_str(identifier);
let identifr = NSString::alloc(nil).init_str(&identifier);
let alloc: id = msg_send![class!(NSToolbarItem), alloc];
let item: id = msg_send![alloc, initWithItemIdentifier:identifier];
let item: id = msg_send![alloc, initWithItemIdentifier:identifr];
Id::from_ptr(item)
};
@ -39,6 +41,7 @@ impl<'a> ToolbarItem<'a> {
}
}
/// Sets the title for this item.
pub fn set_title(&mut self, title: &str) {
unsafe {
let title = NSString::alloc(nil).init_str(title);
@ -46,6 +49,7 @@ impl<'a> ToolbarItem<'a> {
}
}
/// Sets and takes ownership of the button for this item.
pub fn set_button(&mut self, button: Button) {
button.set_bezel_style(11);
@ -56,6 +60,7 @@ impl<'a> ToolbarItem<'a> {
self.button = Some(button);
}
/// Sets the minimum size for this button.
pub fn set_min_size(&mut self, width: f64, height: f64) {
unsafe {
let size = NSSize::new(width.into(), height.into());
@ -63,6 +68,7 @@ impl<'a> ToolbarItem<'a> {
}
}
/// Sets the maximum size for this button.
pub fn set_max_size(&mut self, width: f64, height: f64) {
unsafe {
let size = NSSize::new(width.into(), height.into());

View file

@ -1,7 +1,10 @@
//! Module hoisting.
pub mod toolbar;
pub use toolbar::{Toolbar, ToolbarDelegate};
pub mod item;
pub use item::ToolbarItem;
pub mod traits;
pub use traits::ToolbarController;
pub mod toolbar;
pub use toolbar::Toolbar;

View file

@ -3,127 +3,144 @@
//!
//! UNFORTUNATELY, this is a very old and janky API. So... yeah.
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Once;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSArray, NSString};
use objc_id::Id;
use objc_id::ShareId;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::constants::TOOLBAR_PTR;
use crate::toolbar::item::ToolbarItem;
use crate::utils::str_from;
/// 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;
}
use crate::toolbar::traits::ToolbarController;
use crate::utils::{load, str_from};
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSToolbar` and associated delegate live.
pub struct Toolbar {
pub inner: Id<Object>,
pub objc_delegate: Id<Object>,
pub delegate: Box<dyn ToolbarDelegate>
pub struct Toolbar<T> {
internal_callback_ptr: *const RefCell<T>,
pub identifier: String,
pub objc_controller: ShareId<Object>,
pub controller: Rc<RefCell<T>>
}
impl Toolbar {
impl<T> Toolbar<T> where T: ToolbarController + 'static {
/// 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!(NSToolbar), alloc];
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
Id::from_ptr(toolbar)
pub fn new<S: Into<String>>(identifier: S, controller: T) -> Self {
let identifier = identifier.into();
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let toolbar_delegate = Box::new(delegate);
let objc_controller = unsafe {
let delegate_class = register_delegate_class::<T>();
let identifier = NSString::alloc(nil).init_str(&identifier);
let alloc: id = msg_send![delegate_class, alloc];
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
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)
(&mut *toolbar).set_ivar(TOOLBAR_PTR, internal_callback_ptr as usize);
let _: () = msg_send![toolbar, setDelegate:toolbar];
ShareId::from_ptr(toolbar)
};
Toolbar {
inner: inner,
objc_delegate: objc_delegate,
delegate: toolbar_delegate
internal_callback_ptr: internal_callback_ptr,
identifier: identifier,
objc_controller: objc_controller,
controller: controller
}
}
}
impl<T> Drop for Toolbar<T> {
/// A bit of extra cleanup for delegate callback pointers.
fn drop(&mut self) {
unsafe {
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}
/// 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>>();
extern fn allowed_item_identifiers<T: ToolbarController>(this: &Object, _: Sel, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
unsafe {
let identifiers = {
let t = toolbar.borrow();
(*t).allowed_item_identifiers().iter().map(|identifier| {
NSString::alloc(nil).init_str(identifier)
}).collect::<Vec<id>>()
};
Rc::into_raw(toolbar);
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>>();
extern fn default_item_identifiers<T: ToolbarController>(this: &Object, _: Sel, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
unsafe {
let identifiers = {
let t = toolbar.borrow();
(*t).default_item_identifiers().iter().map(|identifier| {
NSString::alloc(nil).init_str(identifier)
}).collect::<Vec<id>>()
};
Rc::into_raw(toolbar);
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
}
extern fn item_for_identifier<T: ToolbarController>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
let identifier = str_from(identifier);
let mut item = {
let t = toolbar.borrow();
let item = (*t).item_for(identifier);
item
};
Rc::into_raw(toolbar);
&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;
fn register_delegate_class<T: ToolbarController>() -> *const Class {
static mut TOOLBAR_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("RSTToolbarDelegate", superclass).unwrap();
let superclass = class!(NSToolbar);
let mut decl = ClassDecl::new("RSTToolbar", 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);
decl.add_method(sel!(toolbarAllowedItemIdentifiers:), allowed_item_identifiers::<T> as extern fn(&Object, _, _) -> id);
decl.add_method(sel!(toolbarDefaultItemIdentifiers:), default_item_identifiers::<T> as extern fn(&Object, _, _) -> id);
decl.add_method(sel!(toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:), item_for_identifier::<T> as extern fn(&Object, _, _, _, _) -> id);
TOOLBAR_DELEGATE_CLASS = decl.register();
TOOLBAR_CLASS = decl.register();
});
unsafe { TOOLBAR_DELEGATE_CLASS }
unsafe { TOOLBAR_CLASS }
}

View file

@ -0,0 +1,17 @@
//! Traits that can be used for Toolbar construction. Relatively straightforward, as far as these
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
//! to pull request it.
use crate::toolbar::item::ToolbarItem;
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
pub trait ToolbarController {
/// 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;
}

View file

@ -1,7 +1,6 @@
//! A wrapper for `NSViewController`. Uses interior mutability to
use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use cocoa::base::{id, nil, YES};

View file

@ -13,8 +13,7 @@ use objc_id::ShareId;
use crate::view::traits::Node;
//use crate::toolbar::traits::ToolbarDelegate;
use crate::toolbar::{Toolbar, ToolbarController};
#[derive(Debug, Default, Clone)]
pub struct WindowHandle(pub Option<ShareId<Object>>);
@ -90,24 +89,15 @@ impl WindowHandle {
}
}
/// Used for setting a toolbar on this window. Note that this takes ownership of whatever
/// `ToolbarDelegate` you pass! The underlying `NSToolbar` is a bit... old, and it's just
/// easier to do things this way.
///
/// If you find yourself in a position where you need your toolbar after the fact, you
/// probably have bigger issues.
//pub fn set_toolbar<T: ToolbarDelegate + 'static>(&mut self, identifier: &str, toolbar: T) {
/*let toolbar = Toolbar::new(identifier, toolbar);
/// Used for setting a toolbar on this window.
pub fn set_toolbar<TB: ToolbarController>(&self, toolbar: &Toolbar<TB>) {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setToolbar:&*toolbar.inner];
let _: () = msg_send![window, setToolbar:&*toolbar.objc_controller];
}
}
self.toolbar = Some(toolbar);*/
//}
}
/// Used for setting the content view controller for this window.
pub fn set_content_view_controller<T: Node + 'static>(&self, view_controller: &T) {

View file

@ -10,7 +10,7 @@ use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::constants::WINDOW_CONTROLLER_PTR;
use crate::toolbar::{Toolbar, ToolbarDelegate};
use crate::toolbar::{Toolbar, ToolbarController};
use crate::view::traits::Node;
use crate::window::handle::WindowHandle;
use crate::window::traits::WindowController;
@ -92,6 +92,11 @@ impl<T> Window<T> where T: WindowController + 'static {
self.objc_controller.set_title(title.into());
}
/// Sets the toolbar for this window.
pub fn set_toolbar<TB: ToolbarController>(&self, toolbar: &Toolbar<TB>) {
self.objc_controller.set_toolbar(toolbar);
}
/// Sets the content view controller for the window.
pub fn set_content_view_controller<VC: Node + 'static>(&self, view_controller: &VC) {
self.objc_controller.set_content_view_controller(view_controller);