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:
parent
22f96bb238
commit
a167be8383
|
@ -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"
|
||||
|
||||
|
|
|
@ -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.
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -246,8 +246,7 @@ impl UserDefaults {
|
|||
|
||||
match result {
|
||||
YES => true,
|
||||
NO => false,
|
||||
_ => unreachable!()
|
||||
NO => false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
165
src/foundation/class.rs
Normal file
165
src/foundation/class.rs
Normal 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
|
||||
);
|
||||
}
|
|
@ -72,8 +72,7 @@ impl NSData {
|
|||
|
||||
match result {
|
||||
YES => true,
|
||||
NO => false,
|
||||
_ => unreachable!()
|
||||
NO => false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,7 @@ impl NSString {
|
|||
|
||||
match result {
|
||||
YES => true,
|
||||
NO => false,
|
||||
_ => unreachable!()
|
||||
NO => false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
43
src/image/icons.rs
Normal file
43
src/image/icons.rs
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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] }),
|
||||
|
|
|
@ -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 destination’s 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) {}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 it’s 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
133
src/switch.rs
Normal 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 }
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
38
src/utils.rs
38
src/utils.rs
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, _, _)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 destination’s 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) {}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue