More or less finalized Toolbar support, at least for now

This commit is contained in:
Ryan McGrath 2020-03-12 12:33:41 -07:00
parent db33c382b7
commit 3d93b04455
No known key found for this signature in database
GPG key ID: 811674B62B666830
9 changed files with 293 additions and 89 deletions

View file

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

View file

@ -0,0 +1,91 @@
//! Handles the Objective-C functionality for the Toolbar module.
use std::rc::Rc;
use std::sync::Once;
use cocoa::base::{id, nil};
use cocoa::foundation::{NSArray, NSString};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::constants::TOOLBAR_PTR;
use crate::toolbar::traits::ToolbarController;
use crate::utils::{load, str_from};
/// Retrieves and passes the allowed item identifiers for this toolbar.
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)
}
}
/// Retrieves and passes the default item identifiers for this toolbar.
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)
}
}
/// Loads the controller, grabs whatever item is for this identifier, and returns what the
/// Objective-C runtime needs.
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 a `NSToolbar` subclass, and configures it to hold some ivars for various things we need
/// to store. We use it as our delegate as well, just to cut down on moving pieces.
pub(crate) fn register_toolbar_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!(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::<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_CLASS = decl.register();
});
unsafe { TOOLBAR_CLASS }
}

View file

@ -0,0 +1,55 @@
//! A wrapper for the underlying `NSToolbar`, which is safe to clone and pass around. We do this to
//! provide a uniform and expectable API.
use cocoa::base::{YES, NO};
use cocoa::foundation::{NSUInteger};
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::toolbar::types::{ToolbarDisplayMode, ToolbarSizeMode};
#[derive(Clone, Debug)]
pub struct ToolbarHandle(pub ShareId<Object>);
impl ToolbarHandle {
/// Indicates whether the toolbar shows the separator between the toolbar and the main window
/// contents.
pub fn set_shows_baseline_separator(&self, shows: bool) {
unsafe {
let _: () = msg_send![&*self.0, setShowsBaselineSeparator:match shows {
true => YES,
false => NO
}];
}
}
/// Sets the toolbar's display mode.
pub fn set_display_mode(&self, mode: ToolbarDisplayMode) {
let mode: NSUInteger = mode.into();
unsafe {
let _: () = msg_send![&*self.0, setDisplayMode:mode];
}
}
/// Sets the toolbar's size mode.
pub fn set_size_mode(&self, mode: ToolbarSizeMode) {
let mode: NSUInteger = mode.into();
unsafe {
let _: () = msg_send![&*self.0, setSizeMode:mode];
}
}
/// Set whether the toolbar is visible or not.
pub fn set_visible(&self, visibility: bool) {
unsafe {
let _: () = msg_send![&*self.0, setVisible:match visibility {
true => YES,
false => NO
}];
}
}
}

View file

@ -1,4 +1,39 @@
//! Module hoisting. //! This module contains a wrapper for `NSToolbar`, one of the standard UI elements in a native
//! Cocoa application. To customize it and provide options, you should implement a
//! `ToolbarController` for your desired struct, and instantiate your `Toolbar` with it. For
//! example:
//!
//! ```
//! use appkit::prelude::*;
//!
//! #[derive(Default)]
//! struct WindowToolbar;
//!
//! impl ToolbarController for WindowToolbar {
//! /* Your trait implementation here */
//! }
//!
//! ```
//!
//! And then, wherever your window is:
//!
//! ```
//! #[derive(Default)]
//! struct AppWindow {
//! pub toolbar: Toolbar<WindowToolbar>
//! }
//!
//! impl WindowController for AppWindow {
//! fn did_load(&mut self, window: WindowHandle) {
//! window.set_toolbar(&self.toolbar);
//! }
//! }
//! ```
pub(crate) mod class;
pub mod handle;
pub use handle::ToolbarHandle;
pub mod item; pub mod item;
pub use item::ToolbarItem; pub use item::ToolbarItem;
@ -8,3 +43,6 @@ pub use traits::ToolbarController;
pub mod toolbar; pub mod toolbar;
pub use toolbar::Toolbar; pub use toolbar::Toolbar;
pub mod types;
pub use types::*;

View file

@ -5,26 +5,33 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Once;
use cocoa::base::{id, nil}; use cocoa::base::{id, nil};
use cocoa::foundation::{NSArray, NSString}; use cocoa::foundation::NSString;
use objc_id::ShareId; use objc_id::ShareId;
use objc::declare::ClassDecl; use objc::{msg_send, sel, sel_impl};
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::constants::TOOLBAR_PTR; use crate::constants::TOOLBAR_PTR;
use crate::toolbar::class::register_toolbar_class;
use crate::toolbar::handle::ToolbarHandle;
use crate::toolbar::traits::ToolbarController; use crate::toolbar::traits::ToolbarController;
use crate::utils::{load, str_from}; use crate::toolbar::types::{ToolbarDisplayMode, ToolbarSizeMode};
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime /// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSToolbar` and associated delegate live. /// where our `NSToolbar` and associated delegate live.
pub struct Toolbar<T> { pub struct Toolbar<T> {
/// A pointer that we "forget" until dropping this struct. This allows us to keep the retain
/// count of things appropriate until the Toolbar is done.
internal_callback_ptr: *const RefCell<T>, internal_callback_ptr: *const RefCell<T>,
/// An internal identifier used by the toolbar. We cache it here in case users want it.
pub identifier: String, pub identifier: String,
pub objc_controller: ShareId<Object>,
/// The Objective-C runtime controller (the toolbar, really - it does double duty).
pub objc_controller: ToolbarHandle,
/// The user supplied controller.
pub controller: Rc<RefCell<T>> pub controller: Rc<RefCell<T>>
} }
@ -41,7 +48,7 @@ impl<T> Toolbar<T> where T: ToolbarController + 'static {
}; };
let objc_controller = unsafe { let objc_controller = unsafe {
let delegate_class = register_delegate_class::<T>(); let delegate_class = register_toolbar_class::<T>();
let identifier = NSString::alloc(nil).init_str(&identifier); let identifier = NSString::alloc(nil).init_str(&identifier);
let alloc: id = msg_send![delegate_class, alloc]; let alloc: id = msg_send![delegate_class, alloc];
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier]; let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
@ -52,95 +59,49 @@ impl<T> Toolbar<T> where T: ToolbarController + 'static {
ShareId::from_ptr(toolbar) ShareId::from_ptr(toolbar)
}; };
{
let mut c = controller.borrow_mut();
(*c).did_load(ToolbarHandle(objc_controller.clone()));
}
Toolbar { Toolbar {
internal_callback_ptr: internal_callback_ptr, internal_callback_ptr: internal_callback_ptr,
identifier: identifier, identifier: identifier,
objc_controller: objc_controller, objc_controller: ToolbarHandle(objc_controller),
controller: controller controller: controller
} }
} }
/// Indicates whether the toolbar shows the separator between the toolbar and the main window
/// contents.
pub fn set_shows_baseline_separator(&self, shows: bool) {
self.objc_controller.set_shows_baseline_separator(shows);
}
/// Sets the toolbar's display mode.
pub fn set_display_mode(&self, mode: ToolbarDisplayMode) {
self.objc_controller.set_display_mode(mode);
}
/// Sets the toolbar's size mode.
pub fn set_size_mode(&self, mode: ToolbarSizeMode) {
self.objc_controller.set_size_mode(mode);
}
/// Set whether the toolbar is visible or not.
pub fn set_visible(&self, visibility: bool) {
self.objc_controller.set_visible(visibility);
}
} }
impl<T> Drop for Toolbar<T> { impl<T> Drop for Toolbar<T> {
/// A bit of extra cleanup for delegate callback pointers. /// A bit of extra cleanup for delegate callback pointers.
/// Note: this currently doesn't check to see if it needs to be removed from a Window it's
/// attached to. In theory this is fine... in practice (and in Rust) it might be wonky, so
/// worth circling back on at some point.
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let _ = Rc::from_raw(self.internal_callback_ptr); let _ = Rc::from_raw(self.internal_callback_ptr);
} }
} }
} }
/// Loops back to the delegate.
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<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<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<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!(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::<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_CLASS = decl.register();
});
unsafe { TOOLBAR_CLASS }
}

View file

@ -2,10 +2,16 @@
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free //! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
//! to pull request it. //! to pull request it.
use crate::toolbar::handle::ToolbarHandle;
use crate::toolbar::item::ToolbarItem; use crate::toolbar::item::ToolbarItem;
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`. /// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
pub trait ToolbarController { pub trait ToolbarController {
/// This method can be used to configure your toolbar, if you need to do things involving the
/// handle. Unlike some other view types, it's not strictly necessary, and is provided in the
/// interest of a uniform and expectable API.
fn did_load(&mut self, _toolbar: ToolbarHandle) {}
/// What items are allowed in this toolbar. /// What items are allowed in this toolbar.
fn allowed_item_identifiers(&self) -> Vec<&'static str>; fn allowed_item_identifiers(&self) -> Vec<&'static str>;

View file

@ -0,0 +1,53 @@
//! Various types used for Toolbar configuration.
use cocoa::foundation::NSUInteger;
/// Represents the display mode(s) a Toolbar can render in.
#[derive(Clone, Copy, Debug)]
pub enum ToolbarDisplayMode {
/// The default display mode.
Default,
/// Show icon and label.
IconAndLabel,
/// Show icon only.
IconOnly,
/// Show label only.
LabelOnly
}
impl From<ToolbarDisplayMode> for NSUInteger {
fn from(mode: ToolbarDisplayMode) -> Self {
match mode {
ToolbarDisplayMode::Default => 0,
ToolbarDisplayMode::IconAndLabel => 1,
ToolbarDisplayMode::IconOnly => 2,
ToolbarDisplayMode::LabelOnly => 3
}
}
}
/// Represents the size mode a Toolbar can use.
#[derive(Clone, Copy, Debug)]
pub enum ToolbarSizeMode {
/// The default size mode.
Default,
/// The regular size mode.
Regular,
/// The small size mode.
Small
}
impl From<ToolbarSizeMode> for NSUInteger {
fn from(mode: ToolbarSizeMode) -> Self {
match mode {
ToolbarSizeMode::Default => 0,
ToolbarSizeMode::Regular => 1,
ToolbarSizeMode::Small => 2
}
}
}

View file

@ -90,11 +90,11 @@ impl WindowHandle {
} }
/// Used for setting a toolbar on this window. /// Used for setting a toolbar on this window.
pub fn set_toolbar<TB: ToolbarController>(&self, toolbar: &Toolbar<TB>) { pub fn set_toolbar<TC: ToolbarController>(&self, toolbar: &Toolbar<TC>) {
if let Some(controller) = &self.0 { if let Some(controller) = &self.0 {
unsafe { unsafe {
let window: id = msg_send![*controller, window]; let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setToolbar:&*toolbar.objc_controller]; let _: () = msg_send![window, setToolbar:&*toolbar.objc_controller.0];
} }
} }
} }

View file

@ -93,7 +93,7 @@ impl<T> Window<T> where T: WindowController + 'static {
} }
/// Sets the toolbar for this window. /// Sets the toolbar for this window.
pub fn set_toolbar<TB: ToolbarController>(&self, toolbar: &Toolbar<TB>) { pub fn set_toolbar<TC: ToolbarController + 'static>(&self, toolbar: &Toolbar<TC>) {
self.objc_controller.set_toolbar(toolbar); self.objc_controller.set_toolbar(toolbar);
} }