More features and cleanup.

- Reconfigured subclass creation, as there was a
  subtle-but-big-when-it-hit bug in the prior method where bridge
  callbacks would lose context of the appropriate delegate when calling
  the trait method. The new approach found in `src/foundation/class.rs`
  maps and caches subclass creation, and subclasses are now more
  apparent when debugging from the Objective-C side as we can carry
  their intended name/usage through. Not applied to all yet, but
  eventually.

- Cleaned up a number of linter warnings that had grown over time.

- Delegate traits now require an associated const `NAME`, which is used
  for subclass creation.

- (macOS) Toolbars now supported setting selected items, which is
  typically used in preferences screens.

- (macOS) Windows now support setting the toolbar display style. On Big
  Sur, this works as intended - it's a noop on older OS's that don't
  support it.

- Support for system icons for macOS preferences windows.

Still a bit to go to flesh this all out, but it's getting there - at
which point then iOS supoort can be folded in easier.
This commit is contained in:
Ryan McGrath 2021-02-07 20:25:56 -08:00
parent 22f96bb238
commit a167be8383
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
41 changed files with 722 additions and 231 deletions

View file

@ -21,6 +21,7 @@ lazy_static = "1.4.0"
libc = "0.2"
objc = "0.2.7"
objc_id = "0.1.1"
os_info = "3.0.1"
uuid = { version = "0.8", features = ["v4"], optional = true }
url = "2.1.1"

View file

@ -92,12 +92,11 @@ impl Layout for Button {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, view: &V) {
/*let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}*/
fn add_subview<V: Layout>(&self, _view: &V) {
panic!(r#"
Tried to add a subview to a Button. This is not allowed in Cacao. If you think this should be supported,
open a discussion on the GitHub repo.
"#);
}
}

View file

@ -246,8 +246,7 @@ impl UserDefaults {
match result {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}

165
src/foundation/class.rs Normal file
View file

@ -0,0 +1,165 @@
use std::collections::HashMap;
use std::ffi::CString;
use std::sync::{Arc, RwLock};
use lazy_static::lazy_static;
use objc::declare::ClassDecl;
use objc::runtime::{objc_getClass, Class};
lazy_static! {
static ref CLASSES: ClassMap = ClassMap::new();
}
/// A ClassMap is a general cache for our Objective-C class lookup and registration. Rather than
/// constantly calling into the runtime, we store pointers to Class types here after first lookup
/// and/or creation. The general store format is (roughly speaking) as follows:
///
/// ```
/// {
/// "subclass_type": {
/// "superclass_type": *const Class as usize
/// }
/// }
/// ```
///
/// The reasoning behind the double map is that it allows for lookup without allocating a `String`
/// on each hit; allocations are only required when creating a Class to inject, purely for naming
/// and debugging reasons.
///
/// There may be a way to do this without using HashMaps and avoiding the heap, but working and
/// usable beats ideal for now. Open to suggestions.
#[derive(Debug)]
pub(crate) struct ClassMap(RwLock<HashMap<&'static str, HashMap<&'static str, usize>>>);
impl ClassMap {
/// Returns a new ClassMap.
pub fn new() -> Self {
ClassMap(RwLock::new({
let mut map = HashMap::new();
// Top-level classes, like `NSView`, we cache here. The reasoning is that if a subclass
// is being created, we can avoid querying the runtime for the superclass - i.e, many subclasses
// will have `NSView` as their superclass.
map.insert("_supers", HashMap::new());
map
}))
}
/// Attempts to load a previously registered subclass.
pub fn load_subclass(&self, subclass_name: &'static str, superclass_name: &'static str) -> Option<*const Class> {
let reader = self.0.read().unwrap();
if let Some(inner) = (*reader).get(subclass_name) {
if let Some(class) = inner.get(superclass_name) {
return Some(*class as *const Class);
}
}
None
}
/// Store a newly created subclass type.
pub fn store_subclass(
&self,
subclass_name: &'static str,
superclass_name: &'static str,
class: *const Class,
) {
let mut writer = self.0.write().unwrap();
if let Some(map) = (*writer).get_mut(subclass_name) {
map.insert(superclass_name, class as usize);
} else {
let mut map = HashMap::new();
map.insert(superclass_name, class as usize);
(*writer).insert(subclass_name, map);
}
}
/// Attempts to load a Superclass. This first checks for the cached pointer; if not present, it
/// will load the superclass from the Objective-C runtime and cache it for future lookup. This
/// assumes that the class is one that should *already* and *always* exist in the runtime, and
/// by design will panic if it can't load the correct superclass, as that would lead to very
/// invalid behavior.
pub fn load_superclass(&self, name: &'static str) -> Option<*const Class> {
{
let reader = self.0.read().unwrap();
if let Some(superclass) = (*reader)["_supers"].get(name) {
return Some(*superclass as *const Class);
}
}
let objc_superclass_name = CString::new(name).unwrap();
let superclass = unsafe {
objc_getClass(objc_superclass_name.as_ptr() as *const _)
};
// This should not happen, for our use-cases, but it's conceivable that this could actually
// be expected, so just return None and let the caller panic if so desired.
if superclass.is_null() {
return None;
}
{
let mut writer = self.0.write().unwrap();
if let Some(supers) = (*writer).get_mut("_supers") {
supers.insert(name, superclass as usize);
}
}
Some(superclass)
}
}
/// Attempts to load a subclass, given a `superclass_name` and subclass_name. If
/// the subclass cannot be loaded, it's dynamically created and injected into
/// the runtime, and then returned. The returned value can be used for allocating new instances of
/// this class in the Objective-C runtime.
///
/// The `config` block can be used to customize the Class declaration before it's registered with
/// the runtime. This is useful for adding method handlers and ivar storage.
///
/// If the superclass cannot be loaded, this will panic. If the subclass cannot be
/// created, this will panic. In general, this is expected to work, and if it doesn't,
/// the entire framework will not really work.
///
/// There's definitely room to optimize here, but it works for now.
#[inline(always)]
pub fn load_or_register_class<F>(
superclass_name: &'static str,
subclass_name: &'static str,
config: F
) -> *const Class
where
F: Fn(&mut ClassDecl) + 'static
{
if let Some(subclass) = CLASSES.load_subclass(subclass_name, superclass_name) {
return subclass;
}
if let Some(superclass) = CLASSES.load_superclass(superclass_name) {
let objc_subclass_name = format!("{}_{}", subclass_name, superclass_name);
match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) {
Some(mut decl) => {
config(&mut decl);
let class = decl.register();
CLASSES.store_subclass(subclass_name, superclass_name, class);
return class;
},
None => {
panic!("Subclass of type {}_{} could not be allocated.", subclass_name, superclass_name);
}
}
}
panic!(
"Attempted to create subclass for {}, but unable to load superclass of type {}.",
subclass_name,
superclass_name
);
}

View file

@ -72,8 +72,7 @@ impl NSData {
match result {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}

View file

@ -27,6 +27,9 @@ pub use autoreleasepool::AutoReleasePool;
mod array;
pub use array::NSArray;
mod class;
pub use class::load_or_register_class;
mod data;
pub use data::NSData;

View file

@ -92,8 +92,7 @@ impl NSNumber {
match result {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
@ -105,8 +104,7 @@ impl NSNumber {
match result {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}

View file

@ -41,8 +41,7 @@ impl NSString {
match result {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}

43
src/image/icons.rs Normal file
View file

@ -0,0 +1,43 @@
/// These icons are system-provided icons that are guaranteed to exist in all versions of macOS
/// that Cacao supports. These will use SFSymbols on Big Sur and onwards (11.0+), and the correct
/// controls for prior macOS versions.
///
/// Note that this list is by default small, as icons that match across OS's is limited in nature.
/// You'll need to determine if and/or how you choose to support icons for systems older than Big
/// Sur; SFSymbols does not exist on Catalina, Mojave, and earlier.
///
/// You can opt to include vector assets in your bundle, or draw icons with `Image::draw` by
/// converting Core Graphics calls (e.g, PaintCode can work well for this).
#[cfg(feature = "macos")]
#[derive(Debug)]
pub enum MacSystemIcon {
/// A standard "General" preferences icon. This is intended for usage in Preferences toolbars.
PreferencesGeneral,
/// A standard "Advanced" preferences icon. This is intended for usage in Preferences toolbars.
PreferencesAdvanced,
/// A standard "Accounts" preferences icon. This is intended for usage in Preferences toolbars.
PreferencesUserAccounts
}
impl MacSystemIcon {
/// Maps system icons to their pre-11.0 framework identifiers.
pub fn to_str(&self) -> &'static str {
match self {
MacSystemIcon::PreferencesGeneral => "NSPreferencesGeneral",
MacSystemIcon::PreferencesAdvanced => "NSAdvanced",
MacSystemIcon::PreferencesUserAccounts => "NSUserAccounts",
}
}
/// Maps system icons to their SFSymbols-counterparts for use on 11.0+.
pub fn to_sfsymbol_str(&self) -> &'static str {
match self {
MacSystemIcon::PreferencesGeneral => "gearshape",
MacSystemIcon::PreferencesAdvanced => "slider.vertical.3",
MacSystemIcon::PreferencesUserAccounts => "person.crop.circle"
}
}
}

View file

@ -11,7 +11,9 @@ use core_graphics::{
};
use core_graphics::context::{CGContext, CGContextRef};
use crate::foundation::{id, YES, NO};
use crate::foundation::{id, YES, NO, NSString};
use crate::utils::os;
use super::icons::*;
#[derive(Debug)]
pub enum ResizeBehavior {
@ -42,21 +44,21 @@ fn min_cgfloat(x: CGFloat, y: CGFloat) -> CGFloat {
impl ResizeBehavior {
pub fn apply(&self, source: CGRect, target: CGRect) -> CGRect {
// if equal, just return source
if(
if
source.origin.x == target.origin.x &&
source.origin.y == target.origin.y &&
source.size.width == target.size.width &&
source.size.height == target.size.height
) {
{
return source;
}
if(
if
source.origin.x == 0. &&
source.origin.y == 0. &&
source.size.width == 0. &&
source.size.height == 0.
) {
{
return source;
}
@ -110,6 +112,26 @@ impl Image {
})
}
/// Returns a stock system icon. These are guaranteed to exist across all versions of macOS
/// supported.
#[cfg(feature = "macos")]
pub fn system_icon(icon: MacSystemIcon, accessibility_description: &str) -> Self {
Image(unsafe {
ShareId::from_ptr(match os::is_minimum_version(11) {
true => {
let icon = NSString::new(icon.to_sfsymbol_str()).into_inner();
let desc = NSString::new(accessibility_description).into_inner();
msg_send![class!(NSImage), imageWithSystemSymbolName:icon accessibilityDescription:desc]
},
false => {
let icon = NSString::new(icon.to_str()).into_inner();
msg_send![class!(NSImage), imageNamed:icon]
}
})
})
}
/// Draw a custom image and get it back as a returned `Image`.
pub fn draw<F>(config: DrawConfig, handler: F) -> Self
where

View file

@ -28,7 +28,7 @@ pub(crate) fn register_image_view_class() -> *const Class {
INIT.call_once(|| unsafe {
let superclass = class!(NSImageView);
let mut decl = ClassDecl::new("RSTImageView", superclass).unwrap();
let decl = ClassDecl::new("RSTImageView", superclass).unwrap();
//decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);

View file

@ -21,6 +21,9 @@ use ios::register_image_view_class;
mod image;
pub use image::{Image, DrawConfig, ResizeBehavior};
mod icons;
pub use icons::*;
/// A helper method for instantiating view classes and applying default settings to them.
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
unsafe {

View file

@ -28,7 +28,7 @@ pub(crate) fn register_view_class() -> *const Class {
INIT.call_once(|| unsafe {
let superclass = class!(NSTextField);
let mut decl = ClassDecl::new("RSTTextInputField", superclass).unwrap();
let decl = ClassDecl::new("RSTTextInputField", superclass).unwrap();
VIEW_CLASS = decl.register();
});

View file

@ -30,7 +30,7 @@ pub static ACTION_CALLBACK_PTR: &str = "rstTargetActionPtr";
/// Point is, Button aren't created that much in the grand scheme of things,
/// and the heap isn't our enemy in a GUI framework anyway. If someone knows
/// a better way to do this that doesn't require double-boxing, I'm all ears.
pub struct Action(Box<Fn() + Send + Sync + 'static>);
pub struct Action(Box<dyn Fn() + Send + Sync + 'static>);
impl fmt::Debug for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {

View file

@ -108,6 +108,7 @@ pub mod notification_center;
pub mod pasteboard;
pub mod progress;
pub mod scrollview;
pub mod switch;
pub mod text;
#[cfg(feature = "quicklook")]

View file

@ -14,7 +14,7 @@ use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, sel, sel_impl, msg_send};
use objc_id::Id;
use crate::foundation::{id, YES, NO, NSArray, NSInteger, NSUInteger};
use crate::foundation::{load_or_register_class, id, YES, NO, NSArray, NSInteger, NSUInteger};
use crate::dragdrop::DragInfo;
use crate::listview::{
LISTVIEW_DELEGATE_PTR, LISTVIEW_CELL_VENDOR_PTR,
@ -138,8 +138,7 @@ pub(crate) fn register_listview_class() -> *const Class {
INIT.call_once(|| unsafe {
let superclass = class!(NSTableView);
let mut decl = ClassDecl::new("RSTListView", superclass).unwrap();
let decl = ClassDecl::new("RSTListView", superclass).unwrap();
VIEW_CLASS = decl.register();
});
@ -152,16 +151,8 @@ pub(crate) fn register_listview_class() -> *const Class {
/// need to do. Note that we treat and constrain this as a one-column "list" view to match
/// `UITableView` semantics; if `NSTableView`'s multi column behavior is needed, then it can
/// be added in.
pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSTableView);
let mut decl = ClassDecl::new("RSTListViewWithDelegate", superclass).unwrap();
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
// move.
pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>(instance: &T) -> *const Class {
load_or_register_class("NSTableView", instance.subclass_name(), |decl| unsafe {
decl.add_ivar::<usize>(LISTVIEW_DELEGATE_PTR);
decl.add_ivar::<usize>(LISTVIEW_CELL_VENDOR_PTR);
@ -178,11 +169,5 @@ pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>() -> *c
decl.add_method(sel!(performDragOperation:), perform_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
decl.add_method(sel!(concludeDragOperation:), conclude_drag_operation::<T> as extern fn (&mut Object, _, _));
decl.add_method(sel!(draggingExited:), dragging_exited::<T> as extern fn (&mut Object, _, _));
VIEW_CLASS = decl.register();
});
unsafe {
VIEW_CLASS
}
})
}

View file

@ -90,7 +90,7 @@ use std::cell::RefCell;
use crate::view::ViewDelegate;
pub(crate) type CellFactoryMap = HashMap<&'static str, Box<Fn() -> Box<Any>>>;
pub(crate) type CellFactoryMap = HashMap<&'static str, Box<dyn Fn() -> Box<dyn Any>>>;
#[derive(Clone)]
pub struct CellFactory(pub Rc<RefCell<CellFactoryMap>>);
@ -114,7 +114,7 @@ impl CellFactory {
let mut lock = self.0.borrow_mut();
lock.insert(identifier, Box::new(move || {
let cell = vendor();
Box::new(cell) as Box<Any>
Box::new(cell) as Box<dyn Any>
}));
}
@ -140,9 +140,9 @@ impl CellFactory {
}
/// A helper method for instantiating view classes and applying default settings to them.
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
fn common_init(class: *const Class) -> id {
unsafe {
let tableview: id = msg_send![registration_fn(), new];
let tableview: id = msg_send![class, new];
let _: () = msg_send![tableview, setTranslatesAutoresizingMaskIntoConstraints:NO];
// Let's... make NSTableView into UITableView-ish.
@ -222,7 +222,8 @@ impl Default for ListView {
impl ListView {
/// Returns a default `View`, suitable for
pub fn new() -> Self {
let view = allocate_view(register_listview_class);
let class = register_listview_class();
let view = common_init(class);
#[cfg(target_os = "macos")]
let scrollview = {
@ -265,10 +266,11 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
/// Initializes a new View with a given `ViewDelegate`. This enables you to respond to events
/// and customize the view as a module, similar to class-based systems.
pub fn with(delegate: T) -> ListView<T> {
let class = register_listview_class_with_delegate::<T>(&delegate);
let view = common_init(class);
let mut delegate = Box::new(delegate);
let cell = CellFactory::new();
let view = allocate_view(register_listview_class_with_delegate::<T>);
unsafe {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];

View file

@ -152,15 +152,15 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
/// - When it takes ownership, it "forgets" the pointer - and the `dealloc` method on the
/// backing view cell will clean it up whenever it's dropped.
pub(crate) fn from_cached(view: id) -> ListViewRow<T> {
// @TODO: Make this better.
let delegate = unsafe {
let ptr: usize = *(&*view).get_ivar(LISTVIEW_ROW_DELEGATE_PTR);
let obj = ptr as *mut T;
Box::from_raw(obj)
//&*obj
};
//let delegate = crate::utils::load::<R>(&*view, LISTVIEW_ROW_DELEGATE_PTR);
let mut view = ListViewRow {
let view = ListViewRow {
delegate: Some(delegate),
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),

View file

@ -6,11 +6,23 @@ use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
use crate::layout::Layout;
use crate::view::View;
#[allow(unused_variables)]
pub trait ListViewDelegate {
/// Used to cache subclass creations on the Objective-C side.
/// You can just set this to be the name of your view type. This
/// value *must* be unique per-type.
const NAME: &'static str;
/// You should rarely (read: probably never) need to implement this yourself.
/// It simply acts as a getter for the associated `NAME` const on this trait.
fn subclass_name(&self) -> &'static str {
Self::NAME
}
/// Called when the View is ready to work with. You're passed a `View` - this is safe to
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
/// main thread!
fn did_load(&mut self, _view: ListView) {}
fn did_load(&mut self, view: ListView);
/// Returns the number of items in the list view.
fn number_of_items(&self) -> usize;
@ -19,37 +31,37 @@ pub trait ListViewDelegate {
/// choose to try and work with this. NSTableView & such associated delegate patterns
/// are tricky to support in Rust, and while I have a few ideas about them, I haven't
/// had time to sit down and figure them out properly yet.
fn item_for(&self, _row: usize) -> ListViewRow;
fn item_for(&self, row: usize) -> ListViewRow;
/// An optional delegate method; implement this if you'd like swipe-to-reveal to be
/// supported for a given row by returning a vector of actions to show.
fn actions_for(&self, row: usize, edge: RowEdge) -> Vec<RowAction> { Vec::new() }
/// Called when this is about to be added to the view heirarchy.
fn will_appear(&self, _animated: bool) {}
fn will_appear(&self, animated: bool) {}
/// Called after this has been added to the view heirarchy.
fn did_appear(&self, _animated: bool) {}
fn did_appear(&self, animated: bool) {}
/// Called when this is about to be removed from the view heirarchy.
fn will_disappear(&self, _animated: bool) {}
fn will_disappear(&self, animated: bool) {}
/// Called when this has been removed from the view heirarchy.
fn did_disappear(&self, _animated: bool) {}
fn did_disappear(&self, animated: bool) {}
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }
fn dragging_entered(&self, info: DragInfo) -> DragOperation { DragOperation::None }
/// Invoked when the image is released, allowing the receiver to agree to or refuse drag operation.
fn prepare_for_drag_operation(&self, _info: DragInfo) -> bool { false }
fn prepare_for_drag_operation(&self, info: DragInfo) -> bool { false }
/// Invoked after the released image has been removed from the screen, signaling the receiver to import the pasteboard data.
fn perform_drag_operation(&self, _info: DragInfo) -> bool { false }
fn perform_drag_operation(&self, info: DragInfo) -> bool { false }
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up.
fn conclude_drag_operation(&self, _info: DragInfo) {}
fn conclude_drag_operation(&self, info: DragInfo) {}
/// Invoked when the dragged image exits the destinations bounds rectangle (in the case of a view) or its frame
/// rectangle (in the case of a window object).
fn dragging_exited(&self, _info: DragInfo) {}
fn dragging_exited(&self, info: DragInfo) {}
}

View file

@ -4,7 +4,6 @@
use std::ffi::c_void;
use std::sync::Once;
use std::unreachable;
use block::Block;
@ -108,8 +107,7 @@ extern fn did_update<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn should_handle_reopen<T: AppDelegate>(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL {
match app::<T>(this).should_handle_reopen(match has_visible_windows {
YES => true,
NO => false,
_ => { unreachable!(); },
NO => false
}) {
true => YES,
false => NO
@ -270,8 +268,7 @@ extern fn print_files<T: AppDelegate>(this: &Object, _: Sel, _: id, files: id, s
app::<T>(this).print_files(files, settings, match show_print_panels {
YES => true,
NO => false,
_ => { unreachable!(); }
NO => false
}).into()
}

View file

@ -110,7 +110,7 @@ pub struct App<T = (), M = ()> {
pub objc_delegate: Id<Object>,
pub delegate: Box<T>,
pub pool: AutoReleasePool,
_t: std::marker::PhantomData<M>
_message: std::marker::PhantomData<M>
}
impl<T> App<T> {
@ -119,7 +119,7 @@ impl<T> App<T> {
/// `did_finish_launching`. :)
pub fn run(&self) {
unsafe {
let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
//let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
let shared_app: id = msg_send![class!(RSTApplication), sharedApplication];
let _: () = msg_send![shared_app, run];
self.pool.drain();
@ -159,14 +159,14 @@ impl<T> App<T> where T: AppDelegate + 'static {
inner: inner,
delegate: app_delegate,
pool: pool,
_t: std::marker::PhantomData
_message: std::marker::PhantomData
}
}
}
// This is a very basic "dispatch" mechanism. In macOS, it's critical that UI work happen on the
// UI ("main") thread. We can hook into the standard mechanism for this by dispatching on
// queues; in our case, we'll just offer two points - one for the background queue(s), and one
// queues; in our case, we'll just offer two points - one for a background queue, and one
// for the main queue. They automatically forward through to our registered `AppDelegate`.
//
// One thing I don't like about GCD is that detecting incorrect thread usage has historically been

View file

@ -6,7 +6,7 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl, msg_send};
use crate::foundation::{id, NSArray, NSString};
use crate::foundation::{load_or_register_class, id, NSArray, NSString};
use crate::macos::toolbar::{TOOLBAR_PTR, ToolbarDelegate};
use crate::utils::load;
@ -32,6 +32,17 @@ extern fn default_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _:
identifiers.into_inner()
}
/// Retrieves and passes the default item identifiers for this toolbar.
extern fn selectable_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
let identifiers: NSArray = toolbar.selectable_item_identifiers().iter().map(|identifier| {
NSString::new(identifier).into_inner()
}).collect::<Vec<id>>().into();
identifiers.into_inner()
}
/// Loads the controller, grabs whatever item is for this identifier, and returns what the
/// Objective-C runtime needs.
extern fn item_for_identifier<T: ToolbarDelegate>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id {
@ -47,24 +58,15 @@ extern fn item_for_identifier<T: ToolbarDelegate>(this: &Object, _: Sel, _: id,
/// 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: ToolbarDelegate>() -> *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();
pub(crate) fn register_toolbar_class<T: ToolbarDelegate>(instance: &T) -> *const Class {
load_or_register_class("NSObject", instance.subclass_name(), |decl| unsafe {
// 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!(toolbarSelectableItemIdentifiers:), selectable_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

@ -13,6 +13,7 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, NSString};
use crate::invoker::TargetActionHandler;
use crate::button::Button;
use crate::image::Image;
/// Wraps `NSToolbarItem`. Enables configuring things like size, view, and so on.
#[derive(Debug)]
@ -20,6 +21,7 @@ pub struct ToolbarItem {
pub identifier: String,
pub objc: Id<Object>,
pub button: Option<Button>,
pub image: Option<Image>,
handler: Option<TargetActionHandler>
}
@ -40,6 +42,7 @@ impl ToolbarItem {
identifier: identifier,
objc: objc,
button: None,
image: None,
handler: None
}
}
@ -49,7 +52,6 @@ impl ToolbarItem {
unsafe {
let title = NSString::new(title).into_inner();
let _: () = msg_send![&*self.objc, setLabel:&*title];
let _: () = msg_send![&*self.objc, setTitle:&*title];
}
}
@ -64,6 +66,15 @@ impl ToolbarItem {
self.button = Some(button);
}
/// Sets and takes ownership of the image for this toolbar item.
pub fn set_image(&mut self, image: Image) {
unsafe {
let _: () = msg_send![&*self.objc, setImage:&*image.0];
}
self.image = Some(image);
}
/// Sets the minimum size for this button.
pub fn set_min_size(&mut self, width: f64, height: f64) {
unsafe {

View file

@ -3,7 +3,7 @@
//!
//! UNFORTUNATELY, this is a very old and janky API. So... yeah.
use objc::{msg_send, sel, sel_impl};
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
@ -32,6 +32,8 @@ pub struct Toolbar<T = ()> {
/// The Objective-C runtime toolbar.
pub objc: ShareId<Object>,
pub objc_delegate: ShareId<Object>,
/// The user supplied delegate.
pub delegate: Option<Box<T>>
}
@ -41,23 +43,25 @@ impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
/// chain, and retains it all.
pub fn new<S: Into<String>>(identifier: S, delegate: T) -> Self {
let identifier = identifier.into();
let delegate = Box::new(delegate);
let cls = register_toolbar_class::<T>(&delegate);
let mut delegate = Box::new(delegate);
let objc = unsafe {
let cls = register_toolbar_class::<T>();
let alloc: id = msg_send![cls, alloc];
let (objc, objc_delegate) = unsafe {
let alloc: id = msg_send![class!(NSToolbar), alloc];
let identifier = NSString::new(&identifier);
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
let objc_delegate: id = msg_send![cls, new]; //WithIdentifier:identifier];
let ptr: *const T = &*delegate;
(&mut *toolbar).set_ivar(TOOLBAR_PTR, ptr as usize);
let _: () = msg_send![toolbar, setDelegate:toolbar];
(&mut *objc_delegate).set_ivar(TOOLBAR_PTR, ptr as usize);
let _: () = msg_send![toolbar, setDelegate:objc_delegate];
ShareId::from_ptr(toolbar)
(ShareId::from_ptr(toolbar), ShareId::from_ptr(objc_delegate))
};
&delegate.did_load(Toolbar {
&mut delegate.did_load(Toolbar {
objc: objc.clone(),
objc_delegate: objc_delegate.clone(),
identifier: identifier.clone(),
delegate: None
});
@ -65,6 +69,7 @@ impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
Toolbar {
identifier: identifier,
objc: objc,
objc_delegate: objc_delegate,
delegate: Some(delegate),
}
}
@ -109,6 +114,15 @@ impl<T> Toolbar<T> {
}];
}
}
/// Sets the item represented by the item identifier to be selected.
pub fn set_selected(&self, item_identifier: &str) {
let identifier = NSString::new(item_identifier).into_inner();
unsafe {
let _: () = msg_send![&*self.objc, setSelectedItemIdentifier:identifier];
}
}
}
impl<T> Drop for Toolbar<T> {

View file

@ -6,10 +6,21 @@ use crate::macos::toolbar::{Toolbar, ToolbarItem};
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
pub trait ToolbarDelegate {
/// Used to cache subclass creations on the Objective-C side.
/// You can just set this to be the name of your view type. This
/// value *must* be unique per-type.
const NAME: &'static str;
/// You should rarely (read: probably never) need to implement this yourself.
/// It simply acts as a getter for the associated `NAME` const on this trait.
fn subclass_name(&self) -> &'static str {
Self::NAME
}
/// 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(&self, _toolbar: Toolbar) {}
fn did_load(&mut self, _toolbar: Toolbar) {}
/// What items are allowed in this toolbar.
fn allowed_item_identifiers(&self) -> Vec<&'static str>;
@ -17,6 +28,10 @@ pub trait ToolbarDelegate {
/// The default items in this toolbar.
fn default_item_identifiers(&self) -> Vec<&'static str>;
/// The default items in this toolbar. This defaults to a blank `Vec`, and is an optional
/// method - mostly useful for Preferences windows.
fn selectable_item_identifiers(&self) -> Vec<&'static str> { vec![] }
/// For a given `identifier`, return the `ToolbarItem` that should be displayed.
fn item_for(&self, _identifier: &str) -> &ToolbarItem;
}

View file

@ -9,7 +9,7 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::foundation::{id, BOOL, YES, NO, NSUInteger};
use crate::foundation::{load_or_register_class, id, BOOL, YES, NO, NSUInteger};
use crate::utils::{load, CGSize};
use crate::macos::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
@ -228,33 +228,10 @@ extern fn cancel<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
window.cancel();
}
/// Injects an `NSWindow` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_class() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSWindow);
let decl = ClassDecl::new("RSTWindow", superclass).unwrap();
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
}
/// Injects an `NSWindowDelegate` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_class_with_delegate<T: WindowDelegate>() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSWindow);
let mut decl = ClassDecl::new("RSTWindowWithDelegate", superclass).unwrap();
pub(crate) fn register_window_class_with_delegate<T: WindowDelegate>(instance: &T) -> *const Class {
load_or_register_class("NSWindow", instance.subclass_name(), |decl| unsafe {
decl.add_ivar::<usize>(WINDOW_DELEGATE_PTR);
// NSWindowDelegate methods
@ -302,11 +279,5 @@ pub(crate) fn register_window_class_with_delegate<T: WindowDelegate>() -> *const
decl.add_method(sel!(windowDidExpose:), did_expose::<T> as extern fn(&Object, _, _));
decl.add_method(sel!(windowDidUpdate:), did_update::<T> as extern fn(&Object, _, _));
decl.add_method(sel!(cancelOperation:), cancel::<T> as extern fn (&Object, _, _));
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
})
}

View file

@ -4,7 +4,7 @@
use crate::foundation::NSUInteger;
use crate::geometry::Rect;
use crate::macos::window::enums::WindowStyle;
use crate::macos::window::enums::{WindowStyle, WindowToolbarStyle};
#[derive(Debug)]
pub struct WindowConfig {
@ -22,7 +22,17 @@ pub struct WindowConfig {
/// just before its moved onscreen."_
///
/// You generally just want this to be true, and it's the default for this struct.
pub defer: bool
pub defer: bool,
/// The style of toolbar that should be set here. This one is admittedly odd to be set here,
/// but that's how the underlying API is designed, so we're sticking to it.
///
/// This property is not used on macOS versions prior to Big Sur. This defaults to
/// `ToolbarStyle::Automatic`; consult the specified enum
/// for other variants.
///
/// This setting is notably important for Preferences windows.
pub toolbar_style: WindowToolbarStyle
}
impl Default for WindowConfig {
@ -30,7 +40,8 @@ impl Default for WindowConfig {
let mut config = WindowConfig {
style: 0,
initial_dimensions: Rect::new(100., 100., 1024., 768.),
defer: true
defer: true,
toolbar_style: WindowToolbarStyle::Automatic
};
config.set_styles(&[
@ -54,4 +65,18 @@ impl WindowConfig {
self.style = style;
}
/// Set the initial dimensions of this window.
pub fn set_initial_dimensions(&mut self, top: f64, left: f64, width: f64, height: f64) {
self.initial_dimensions = Rect::new(top, left, width, height);
}
/// Offered as a convenience API to match the others. You can just set this property directly
/// if you prefer.
///
/// Sets the Toolbar style. This is not used prior to macOS Big Sur (11.0); consult the
/// `ToolbarStyle` enum for more information on possible values.
pub fn set_toolbar_style(&mut self, style: WindowToolbarStyle) {
self.toolbar_style = style;
}
}

View file

@ -96,3 +96,39 @@ impl From<TitleVisibility> for NSInteger {
}
}
}
/// Represents the styles a Toolbar can have. This setting is specific to macOS 11.0+ (Big Sur and
/// onwards); setting it won't change versions prior to Big Sur.
#[derive(Clone, Copy, Debug)]
pub enum WindowToolbarStyle {
/// The default display mode. This will change the appearance based on whether it's 10.15 and
/// earlier. In most cases, this is fine.
Automatic,
/// The style from macOS pre-11.0. Toolbar items will always be located underneath the
/// titlebar.
Expanded,
/// A style specifically for Preferences windows. Toolbar items will be under the titlebar, and
/// centered.
Preferences,
/// The Big Sur (11.0+) style. Titles appear next to controls.
Unified,
/// The Big Sur (11.0+) style, but more compact. Toolbar flushes up against the title and
/// spacing is reduced.
UnifiedCompact
}
impl From<WindowToolbarStyle> for NSUInteger {
fn from(mode: WindowToolbarStyle) -> Self {
match mode {
WindowToolbarStyle::Automatic => 0,
WindowToolbarStyle::Expanded => 1,
WindowToolbarStyle::Preferences => 2,
WindowToolbarStyle::Unified => 3,
WindowToolbarStyle::UnifiedCompact => 4
}
}
}

View file

@ -8,8 +8,6 @@
//! not bother providing access to them. If you require functionality like that, you're free to use
//! the `objc` field on a `Window` to instrument it with the Objective-C runtime on your own.
use std::unreachable;
use block::ConcreteBlock;
use core_graphics::base::CGFloat;
@ -26,7 +24,7 @@ use crate::macos::toolbar::{Toolbar, ToolbarDelegate};
use crate::utils::Controller;
mod class;
use class::{register_window_class, register_window_class_with_delegate};
use class::register_window_class_with_delegate;
mod config;
pub use config::WindowConfig;
@ -72,8 +70,7 @@ impl Window {
// apps that would use this toolkit wouldn't be tab-oriented...
let _: () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing:NO];
let alloc: id = msg_send![register_window_class(), alloc];
let alloc: id = msg_send![class!(NSWindow), alloc];
// Other types of backing (Retained/NonRetained) are archaic, dating back to the
// NeXTSTEP era, and are outright deprecated... so we don't allow setting them.
@ -96,6 +93,14 @@ impl Window {
// us.
let _: () = msg_send![window, setReleasedWhenClosed:NO];
let _: () = msg_send![window, setRestorable:NO];
// This doesn't exist prior to Big Sur, but is important to support for Big Sur.
//
// Why this isn't a setting on the Toolbar itself I'll never know.
let toolbar_style: NSUInteger = config.toolbar_style.into();
let _: () = msg_send![window, setToolbarStyle:toolbar_style];
ShareId::from_ptr(window)
};
@ -112,6 +117,7 @@ impl<T> Window<T> where T: WindowDelegate + 'static {
/// enables easier structure of your codebase, and in a way simulates traditional class based
/// architectures... just without the subclassing.
pub fn with(config: WindowConfig, delegate: T) -> Self {
let class = register_window_class_with_delegate::<T>(&delegate);
let mut delegate = Box::new(delegate);
let objc = unsafe {
@ -119,8 +125,7 @@ impl<T> Window<T> where T: WindowDelegate + 'static {
// apps that would use this toolkit wouldn't be tab-oriented...
let _: () = msg_send![class!(NSWindow), setAllowsAutomaticWindowTabbing:NO];
let alloc: id = msg_send![register_window_class_with_delegate::<T>(), alloc];
let alloc: id = msg_send![class, alloc];
// Other types of backing (Retained/NonRetained) are archaic, dating back to the
// NeXTSTEP era, and are outright deprecated... so we don't allow setting them.
@ -151,6 +156,12 @@ impl<T> Window<T> where T: WindowDelegate + 'static {
let _: () = msg_send![window, setRestorable:NO];
// This doesn't exist prior to Big Sur, but is important to support for Big Sur.
//
// Why this isn't a setting on the Toolbar itself I'll never know.
let toolbar_style: NSUInteger = config.toolbar_style.into();
let _: () = msg_send![window, setToolbarStyle:toolbar_style];
ShareId::from_ptr(window)
};
@ -168,7 +179,6 @@ impl<T> Window<T> where T: WindowDelegate + 'static {
}
}
impl<T> Window<T> {
/// Handles setting the title on the underlying window. Allocates and passes an `NSString` over
/// to the Objective C runtime.
@ -311,8 +321,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, isOpaque] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -322,8 +331,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, isMiniaturized] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -362,8 +370,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, isOnActiveSpace] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -373,8 +380,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, isVisible] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -384,8 +390,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, isKeyWindow] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -395,8 +400,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, canBecomeKeyWindow] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -421,8 +425,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, isMainWindow] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}
@ -432,8 +435,7 @@ impl<T> Window<T> {
unsafe {
match msg_send![&*self.objc, canBecomeMainWindow] {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}
}

View file

@ -9,6 +9,17 @@ use crate::macos::window::Window;
/// lifecycle methods, but mix in a few extra things to handle offering configuration tools
/// in lieu of subclasses.
pub trait WindowDelegate {
/// Used to cache subclass creations on the Objective-C side.
/// You can just set this to be the name of your view type. This
/// value *must* be unique per-type.
const NAME: &'static str;
/// You should rarely (read: probably never) need to implement this yourself.
/// It simply acts as a getter for the associated `NAME` const on this trait.
fn subclass_name(&self) -> &'static str {
Self::NAME
}
/// Fires when this window has loaded in memory, and is about to display. This is a good point
/// to set up your views and what not.
///

View file

@ -1,3 +1,5 @@
#[allow(non_camel_case_types)]
use crate::foundation::NSString;
/// An enum that wraps NSNotificationName.
@ -1024,22 +1026,22 @@ pub enum NotificationName {
PDFViewVisiblePagesChanged,
///
kABDatabaseChanged,
KABDatabaseChanged,
///
kABDatabaseChangedExternally,
KABDatabaseChangedExternally,
///
kQuartzFilterManagerDidAddFilter,
KQuartzFilterManagerDidAddFilter,
///
kQuartzFilterManagerDidModifyFilter,
KQuartzFilterManagerDidModifyFilter,
///
kQuartzFilterManagerDidRemoveFilter,
KQuartzFilterManagerDidRemoveFilter,
///
kQuartzFilterManagerDidSelectFilter,
KQuartzFilterManagerDidSelectFilter,
///
EAAccessoryDidConnect,

View file

@ -32,7 +32,7 @@ impl ThumbnailGenerator {
unsafe {
let image = Image::with(msg_send![thumbnail, NSImage]);
let quality = ThumbnailQuality::from(thumbnail_type);
callback(Ok((image, ThumbnailQuality::Low)));
callback(Ok((image, quality)));
}
} else {
let error = Error::new(error);

View file

@ -81,7 +81,7 @@ pub(crate) fn register_scrollview_class() -> *const Class {
INIT.call_once(|| unsafe {
let superclass = class!(NSScrollView);
let mut decl = ClassDecl::new("RSTScrollView", superclass).unwrap();
let decl = ClassDecl::new("RSTScrollView", superclass).unwrap();
VIEW_CLASS = decl.register();
});

133
src/switch.rs Normal file
View file

@ -0,0 +1,133 @@
//! A wrapper for NSSwitch. Currently the epitome of jank - if you're poking around here, expect
//! that this will change at some point.
use std::fmt;
use std::sync::Once;
use objc_id::ShareId;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, BOOL, YES, NO, NSString};
use crate::invoker::TargetActionHandler;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::utils::load;
/// A wrapper for `NSSwitch`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSSwitch` lives.
#[derive(Debug)]
pub struct Switch {
pub objc: ShareId<Object>,
handler: Option<TargetActionHandler>,
/// A pointer to the Objective-C runtime top layout constraint.
pub top: LayoutAnchorY,
/// A pointer to the Objective-C runtime leading layout constraint.
pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint.
pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint.
pub bottom: LayoutAnchorY,
/// A pointer to the Objective-C runtime width layout constraint.
pub width: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime height layout constraint.
pub height: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime center X layout constraint.
pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint.
pub center_y: LayoutAnchorY
}
impl Switch {
/// Creates a new `NSSwitch` instance, configures it appropriately,
/// and retains the necessary Objective-C runtime pointer.
pub fn new(text: &str) -> Self {
let title = NSString::new(text);
let view: id = unsafe {
let button: id = msg_send![register_class(), buttonWithTitle:title target:nil action:nil];
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
let _: () = msg_send![button, setButtonType:3];
button
};
Switch {
handler: None,
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
objc: unsafe { ShareId::from_ptr(view) },
}
}
/// Sets whether this is checked on or off.
pub fn set_checked(&mut self, checked: bool) {
unsafe {
// @TODO: The constants to use here changed back in 10.13ish, so... do we support that,
// or just hide it?
let _: () = msg_send![&*self.objc, setState:match checked {
true => 1,
false => 0
}];
}
}
/// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
let handler = TargetActionHandler::new(&*self.objc, action);
self.handler = Some(handler);
}
}
impl Layout for Switch {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, _view: &V) {
panic!(r#"
Tried to add a subview to a Button. This is not allowed in Cacao. If you think this should be supported,
open a discussion on the GitHub repo.
"#);
}
}
impl Drop for Switch {
// Just to be sure, let's... nil these out. They should be weak references,
// but I'd rather be paranoid and remove them later.
fn drop(&mut self) {
unsafe {
let _: () = msg_send![&*self.objc, setTarget:nil];
let _: () = msg_send![&*self.objc, setAction:nil];
}
}
}
/// Registers an `NSButton` subclass, and configures it to hold some ivars
/// for various things we need to store.
fn register_class() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSButton);
let decl = ClassDecl::new("RSTSwitch", superclass).unwrap();
VIEW_CLASS = decl.register();
});
unsafe { VIEW_CLASS }
}

View file

@ -28,7 +28,7 @@ pub(crate) fn register_view_class() -> *const Class {
INIT.call_once(|| unsafe {
let superclass = class!(NSTextField);
let mut decl = ClassDecl::new("RSTTextField", superclass).unwrap();
let decl = ClassDecl::new("RSTTextField", superclass).unwrap();
VIEW_CLASS = decl.register();
});

View file

@ -10,7 +10,40 @@ use objc::{Encode, Encoding};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::{id, BOOL};
use crate::foundation::{id, BOOL, YES, NO};
pub mod os {
use lazy_static::lazy_static;
use os_info::Version;
lazy_static! {
pub static ref OS_VERSION: os_info::Info = os_info::get();
}
/// In rare cases we need to check whether something is a specific version of macOS. This is a
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
#[inline(always)]
pub fn is_minimum_version(major: u64) -> bool {
match OS_VERSION.version() {
Version::Semantic(maj, _, _) => major >= *maj,
_ => false
}
}
/// In rare cases we need to check whether something is a specific version of macOS. This is a
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
#[inline(always)]
pub fn is_minimum_semversion(major: u64, minor: u64, patch: u64) -> bool {
match OS_VERSION.version() {
Version::Semantic(maj, min, p) => {
major >= *maj && minor >= *min && patch >= *p
},
_ => { false }
}
}
}
/// A generic trait that's used throughout multiple different controls in this framework - acts as
/// a guard for whether something is a (View|etc)Controller. Only needs to return the backing node.
@ -113,7 +146,6 @@ pub fn activate_cocoa_multithreading() {
pub fn as_bool(value: BOOL) -> bool {
match value {
YES => true,
NO => false,
_ => unreachable!()
NO => false
}
}

View file

@ -6,6 +6,7 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::load_or_register_class;
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
use crate::utils::load;
@ -50,24 +51,13 @@ extern fn did_disappear<T: ViewDelegate>(this: &mut Object, _: Sel) {
}
/// Registers an `NSViewDelegate`.
pub(crate) fn register_view_controller_class<T: ViewDelegate + 'static>() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSViewController);
let mut decl = ClassDecl::new("RSTViewController", superclass).unwrap();
pub(crate) fn register_view_controller_class<T: ViewDelegate + 'static>(instance: &T) -> *const Class {
load_or_register_class("NSViewController", instance.subclass_name(), |decl| unsafe {
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
// NSViewDelegate
decl.add_method(sel!(viewWillAppear), will_appear::<T> as extern fn(&mut Object, _));
decl.add_method(sel!(viewDidAppear), did_appear::<T> as extern fn(&mut Object, _));
decl.add_method(sel!(viewWillDisappear), will_disappear::<T> as extern fn(&mut Object, _));
decl.add_method(sel!(viewDidDisappear), did_disappear::<T> as extern fn(&mut Object, _));
VIEW_CLASS = decl.register();
});
unsafe { VIEW_CLASS }
})
}

View file

@ -27,10 +27,11 @@ pub struct ViewController<T> {
impl<T> ViewController<T> where T: ViewDelegate + 'static {
pub fn new(delegate: T) -> Self {
let mut view = View::with(delegate);
let class = register_view_controller_class::<T>(&delegate);
let view = View::with(delegate);
let objc = unsafe {
let vc: id = msg_send![register_view_controller_class::<T>(), new];
let vc: id = msg_send![class, new];
if let Some(delegate)= &view.delegate {
let ptr: *const T = &**delegate;
@ -42,11 +43,6 @@ impl<T> ViewController<T> where T: ViewDelegate + 'static {
ShareId::from_ptr(vc)
};
//let handle = view.clone_as_handle();
//if let Some(view_delegate) = &mut view.delegate {
// view_delegate.did_load(handle);
//}
ViewController {
objc: objc,
view: view

View file

@ -14,7 +14,7 @@ use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, sel, sel_impl};
use objc_id::Id;
use crate::foundation::{id, YES, NO, NSUInteger};
use crate::foundation::{load_or_register_class, id, YES, NO, NSUInteger};
use crate::dragdrop::DragInfo;
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
use crate::utils::load;
@ -95,31 +95,41 @@ pub(crate) fn register_view_class() -> *const Class {
/// Injects an `NSView` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_view_class_with_delegate<T: ViewDelegate>() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSView);
let mut decl = ClassDecl::new("RSTViewWithDelegate", superclass).unwrap();
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
// move.
pub(crate) fn register_view_class_with_delegate<T: ViewDelegate>(instance: &T) -> *const Class {
load_or_register_class("NSView", instance.subclass_name(), |decl| unsafe {
// A pointer to the ViewDelegate instance on the Rust side.
// It's expected that this doesn't move.
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
decl.add_method(
sel!(isFlipped),
enforce_normalcy as extern fn(&Object, _) -> BOOL
);
// Drag and drop operations (e.g, accepting files)
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
decl.add_method(sel!(prepareForDragOperation:), prepare_for_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
decl.add_method(sel!(performDragOperation:), perform_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
decl.add_method(sel!(concludeDragOperation:), conclude_drag_operation::<T> as extern fn (&mut Object, _, _));
decl.add_method(sel!(draggingExited:), dragging_exited::<T> as extern fn (&mut Object, _, _));
decl.add_method(
sel!(draggingEntered:),
dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger
);
VIEW_CLASS = decl.register();
});
decl.add_method(
sel!(prepareForDragOperation:),
prepare_for_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL
);
unsafe {
VIEW_CLASS
}
decl.add_method(
sel!(performDragOperation:),
perform_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL
);
decl.add_method(
sel!(concludeDragOperation:),
conclude_drag_operation::<T> as extern fn (&mut Object, _, _)
);
decl.add_method(
sel!(draggingExited:),
dragging_exited::<T> as extern fn (&mut Object, _, _)
);
})
}

View file

@ -71,9 +71,9 @@ pub use traits::ViewDelegate;
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
/// A helper method for instantiating view classes and applying default settings to them.
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
fn common_init(class: *const Class) -> id {
unsafe {
let view: id = msg_send![registration_fn(), new];
let view: id = msg_send![class, new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
#[cfg(target_os = "macos")]
@ -128,7 +128,7 @@ impl Default for View {
impl View {
/// Returns a default `View`, suitable for
pub fn new() -> Self {
let view = allocate_view(register_view_class);
let view = common_init(register_view_class());
View {
delegate: None,
@ -149,14 +149,15 @@ impl<T> View<T> where T: ViewDelegate + 'static {
/// Initializes a new View with a given `ViewDelegate`. This enables you to respond to events
/// and customize the view as a module, similar to class-based systems.
pub fn with(delegate: T) -> View<T> {
let class = register_view_class_with_delegate(&delegate);
let mut delegate = Box::new(delegate);
let view = allocate_view(register_view_class_with_delegate::<T>);
unsafe {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let ptr: *const T = &*delegate;
let view = unsafe {
let view: id = common_init(class);
let ptr = Box::into_raw(delegate);
(&mut *view).set_ivar(VIEW_DELEGATE_PTR, ptr as usize);
delegate = Box::from_raw(ptr);
view
};
let mut view = View {

View file

@ -3,37 +3,49 @@
use crate::dragdrop::{DragInfo, DragOperation};
use crate::view::View;
#[allow(unused_variables)]
pub trait ViewDelegate {
/// Used to cache subclass creations on the Objective-C side.
/// You can just set this to be the name of your view type. This
/// value *must* be unique per-type.
const NAME: &'static str;
/// You should rarely (read: probably never) need to implement this yourself.
/// It simply acts as a getter for the associated `NAME` const on this trait.
fn subclass_name(&self) -> &'static str {
Self::NAME
}
/// Called when the View is ready to work with. You're passed a `View` - this is safe to
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
/// main thread!
fn did_load(&mut self, _view: View) {}
fn did_load(&mut self, view: View) {}
/// Called when this is about to be added to the view heirarchy.
fn will_appear(&self, _animated: bool) {}
fn will_appear(&self, animated: bool) {}
/// Called after this has been added to the view heirarchy.
fn did_appear(&self, _animated: bool) {}
fn did_appear(&self, animated: bool) {}
/// Called when this is about to be removed from the view heirarchy.
fn will_disappear(&self, _animated: bool) {}
fn will_disappear(&self, animated: bool) {}
/// Called when this has been removed from the view heirarchy.
fn did_disappear(&self, _animated: bool) {}
fn did_disappear(&self, animated: bool) {}
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }
fn dragging_entered(&self, info: DragInfo) -> DragOperation { DragOperation::None }
/// Invoked when the image is released, allowing the receiver to agree to or refuse drag operation.
fn prepare_for_drag_operation(&self, _info: DragInfo) -> bool { false }
fn prepare_for_drag_operation(&self, info: DragInfo) -> bool { false }
/// Invoked after the released image has been removed from the screen, signaling the receiver to import the pasteboard data.
fn perform_drag_operation(&self, _info: DragInfo) -> bool { false }
fn perform_drag_operation(&self, info: DragInfo) -> bool { false }
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up.
fn conclude_drag_operation(&self, _info: DragInfo) {}
fn conclude_drag_operation(&self, info: DragInfo) {}
/// Invoked when the dragged image exits the destinations bounds rectangle (in the case of a view) or its frame
/// rectangle (in the case of a window object).
fn dragging_exited(&self, _info: DragInfo) {}
fn dragging_exited(&self, info: DragInfo) {}
}