diff --git a/appkit/src/lib.rs b/appkit/src/lib.rs index bb88de8..b843aa5 100644 --- a/appkit/src/lib.rs +++ b/appkit/src/lib.rs @@ -49,7 +49,7 @@ pub mod prelude { pub use crate::menu::{Menu, MenuItem}; 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; diff --git a/appkit/src/toolbar/class.rs b/appkit/src/toolbar/class.rs new file mode 100644 index 0000000..b05e996 --- /dev/null +++ b/appkit/src/toolbar/class.rs @@ -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(this: &Object, _: Sel, _: id) -> id { + let toolbar = load::(this, TOOLBAR_PTR); + + unsafe { + let identifiers = { + let t = toolbar.borrow(); + + (*t).allowed_item_identifiers().iter().map(|identifier| { + NSString::alloc(nil).init_str(identifier) + }).collect::>() + }; + + Rc::into_raw(toolbar); + NSArray::arrayWithObjects(nil, &identifiers) + } +} + +/// Retrieves and passes the default item identifiers for this toolbar. +extern fn default_item_identifiers(this: &Object, _: Sel, _: id) -> id { + let toolbar = load::(this, TOOLBAR_PTR); + + unsafe { + let identifiers = { + let t = toolbar.borrow(); + + (*t).default_item_identifiers().iter().map(|identifier| { + NSString::alloc(nil).init_str(identifier) + }).collect::>() + }; + + 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(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id { + let toolbar = load::(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() -> *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::(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_CLASS = decl.register(); + }); + + unsafe { TOOLBAR_CLASS } +} diff --git a/appkit/src/toolbar/handle.rs b/appkit/src/toolbar/handle.rs new file mode 100644 index 0000000..48175ab --- /dev/null +++ b/appkit/src/toolbar/handle.rs @@ -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); + +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 + }]; + } + } +} diff --git a/appkit/src/toolbar/mod.rs b/appkit/src/toolbar/mod.rs index e09d744..e30d423 100644 --- a/appkit/src/toolbar/mod.rs +++ b/appkit/src/toolbar/mod.rs @@ -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 +//! } +//! +//! 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 use item::ToolbarItem; @@ -8,3 +43,6 @@ pub use traits::ToolbarController; pub mod toolbar; pub use toolbar::Toolbar; + +pub mod types; +pub use types::*; diff --git a/appkit/src/toolbar/toolbar.rs b/appkit/src/toolbar/toolbar.rs index 5de843d..02129ac 100644 --- a/appkit/src/toolbar/toolbar.rs +++ b/appkit/src/toolbar/toolbar.rs @@ -5,26 +5,33 @@ use std::cell::RefCell; use std::rc::Rc; -use std::sync::Once; use cocoa::base::{id, nil}; -use cocoa::foundation::{NSArray, NSString}; +use cocoa::foundation::NSString; use objc_id::ShareId; -use objc::declare::ClassDecl; -use objc::runtime::{Class, Object, Sel}; -use objc::{class, msg_send, sel, sel_impl}; +use objc::{msg_send, sel, sel_impl}; use crate::constants::TOOLBAR_PTR; +use crate::toolbar::class::register_toolbar_class; +use crate::toolbar::handle::ToolbarHandle; 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 /// where our `NSToolbar` and associated delegate live. pub struct Toolbar { + /// 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, + + /// An internal identifier used by the toolbar. We cache it here in case users want it. pub identifier: String, - pub objc_controller: ShareId, + + /// The Objective-C runtime controller (the toolbar, really - it does double duty). + pub objc_controller: ToolbarHandle, + + /// The user supplied controller. pub controller: Rc> } @@ -41,7 +48,7 @@ impl Toolbar where T: ToolbarController + 'static { }; let objc_controller = unsafe { - let delegate_class = register_delegate_class::(); + let delegate_class = register_toolbar_class::(); let identifier = NSString::alloc(nil).init_str(&identifier); let alloc: id = msg_send![delegate_class, alloc]; let toolbar: id = msg_send![alloc, initWithIdentifier:identifier]; @@ -52,95 +59,49 @@ impl Toolbar where T: ToolbarController + 'static { ShareId::from_ptr(toolbar) }; + { + let mut c = controller.borrow_mut(); + (*c).did_load(ToolbarHandle(objc_controller.clone())); + } + Toolbar { internal_callback_ptr: internal_callback_ptr, identifier: identifier, - objc_controller: objc_controller, + objc_controller: ToolbarHandle(objc_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 Drop for Toolbar { /// 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) { unsafe { let _ = Rc::from_raw(self.internal_callback_ptr); } } } - -/// Loops back to the delegate. -extern fn allowed_item_identifiers(this: &Object, _: Sel, _: id) -> id { - let toolbar = load::(this, TOOLBAR_PTR); - - unsafe { - let identifiers = { - let t = toolbar.borrow(); - - (*t).allowed_item_identifiers().iter().map(|identifier| { - NSString::alloc(nil).init_str(identifier) - }).collect::>() - }; - - Rc::into_raw(toolbar); - NSArray::arrayWithObjects(nil, &identifiers) - } -} - -/// Loops back to the delegate. -extern fn default_item_identifiers(this: &Object, _: Sel, _: id) -> id { - let toolbar = load::(this, TOOLBAR_PTR); - - unsafe { - let identifiers = { - let t = toolbar.borrow(); - - (*t).default_item_identifiers().iter().map(|identifier| { - NSString::alloc(nil).init_str(identifier) - }).collect::>() - }; - - Rc::into_raw(toolbar); - NSArray::arrayWithObjects(nil, &identifiers) - } -} - -/// Loops back to the delegate. -extern fn item_for_identifier(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id { - let toolbar = load::(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() -> *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::(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_CLASS = decl.register(); - }); - - unsafe { TOOLBAR_CLASS } -} diff --git a/appkit/src/toolbar/traits.rs b/appkit/src/toolbar/traits.rs index 7f56076..fda374f 100644 --- a/appkit/src/toolbar/traits.rs +++ b/appkit/src/toolbar/traits.rs @@ -2,10 +2,16 @@ //! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free //! to pull request it. +use crate::toolbar::handle::ToolbarHandle; use crate::toolbar::item::ToolbarItem; /// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`. 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. fn allowed_item_identifiers(&self) -> Vec<&'static str>; diff --git a/appkit/src/toolbar/types.rs b/appkit/src/toolbar/types.rs new file mode 100644 index 0000000..fd8f889 --- /dev/null +++ b/appkit/src/toolbar/types.rs @@ -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 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 for NSUInteger { + fn from(mode: ToolbarSizeMode) -> Self { + match mode { + ToolbarSizeMode::Default => 0, + ToolbarSizeMode::Regular => 1, + ToolbarSizeMode::Small => 2 + } + } +} diff --git a/appkit/src/window/handle.rs b/appkit/src/window/handle.rs index 66cd570..31b6615 100644 --- a/appkit/src/window/handle.rs +++ b/appkit/src/window/handle.rs @@ -90,11 +90,11 @@ impl WindowHandle { } /// Used for setting a toolbar on this window. - pub fn set_toolbar(&self, toolbar: &Toolbar) { + pub fn set_toolbar(&self, toolbar: &Toolbar) { if let Some(controller) = &self.0 { unsafe { let window: id = msg_send![*controller, window]; - let _: () = msg_send![window, setToolbar:&*toolbar.objc_controller]; + let _: () = msg_send![window, setToolbar:&*toolbar.objc_controller.0]; } } } diff --git a/appkit/src/window/window.rs b/appkit/src/window/window.rs index c605483..236e4da 100644 --- a/appkit/src/window/window.rs +++ b/appkit/src/window/window.rs @@ -93,7 +93,7 @@ impl Window where T: WindowController + 'static { } /// Sets the toolbar for this window. - pub fn set_toolbar(&self, toolbar: &Toolbar) { + pub fn set_toolbar(&self, toolbar: &Toolbar) { self.objc_controller.set_toolbar(toolbar); }