Ongoing efforts, experimenting with stack/heap issues (ObjC/NSObject/AppKit are... mostly heap already), further work on Pasteboard support
This commit is contained in:
parent
d68cbdc450
commit
80ba209413
|
@ -16,6 +16,7 @@ lazy_static = "1"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
objc_id = "0.1.1"
|
objc_id = "0.1.1"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "0.8", features = ["v4"] }
|
||||||
|
url = "2.1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
enable-webview-downloading = []
|
enable-webview-downloading = []
|
||||||
|
|
|
@ -5,8 +5,11 @@
|
||||||
use cocoa::foundation::NSUInteger;
|
use cocoa::foundation::NSUInteger;
|
||||||
|
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
|
use objc::{msg_send, sel, sel_impl};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::pasteboard::Pasteboard;
|
||||||
|
|
||||||
/// Represents operations that can happen for a given drag/drop scenario.
|
/// Represents operations that can happen for a given drag/drop scenario.
|
||||||
pub enum DragOperation {
|
pub enum DragOperation {
|
||||||
/// No drag operations are allowed.
|
/// No drag operations are allowed.
|
||||||
|
@ -56,5 +59,13 @@ pub struct DragInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DragInfo {
|
impl DragInfo {
|
||||||
|
/// Returns a wrapped Pasteboard instance, enabling you to get the contents of whatever is
|
||||||
|
/// being pasted/dragged/dropped/etc.
|
||||||
|
///
|
||||||
|
/// Note: in general, you should not store pasteboards.
|
||||||
|
pub fn get_pasteboard(&self) -> Pasteboard {
|
||||||
|
unsafe {
|
||||||
|
Pasteboard::with(msg_send![&*self.info, draggingPasteboard])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
51
appkit/src/error.rs
Normal file
51
appkit/src/error.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
//! A wrapper for `NSError`, which can be (and is) bubbled up for certain calls in this library. It
|
||||||
|
//! attempts to be thread safe where possible, and extract the "default" usable information out of
|
||||||
|
//! an `NSError`. This might not be what you need, though, so if it's missing something... well,
|
||||||
|
//! it's up for discussion.
|
||||||
|
|
||||||
|
use std::error;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use cocoa::base::id;
|
||||||
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::utils::str_from;
|
||||||
|
|
||||||
|
/// A wrapper around pieces of data extracted from `NSError`. This could be improved: right now, it
|
||||||
|
/// allocates `String` instances when theoretically it could be avoided, and we might be erasing
|
||||||
|
/// certain parts of the `NSError` object that are useful.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AppKitError {
|
||||||
|
pub code: usize,
|
||||||
|
pub domain: String,
|
||||||
|
pub description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppKitError {
|
||||||
|
/// Given an `NSError` (i.e, an id reference) we'll pull out the relevant information and
|
||||||
|
/// configure this. We pull out the information as it makes the error thread safe this way,
|
||||||
|
/// which is... easier, in some cases.
|
||||||
|
pub fn new(error: id) -> Box<Self> {
|
||||||
|
let (code, domain, description) = unsafe {
|
||||||
|
let code: usize = msg_send![error, code];
|
||||||
|
let domain: id = msg_send![error, domain];
|
||||||
|
let description: id = msg_send![error, localizedDescription];
|
||||||
|
|
||||||
|
(code, domain, description)
|
||||||
|
};
|
||||||
|
|
||||||
|
Box::new(AppKitError {
|
||||||
|
code: code,
|
||||||
|
domain: str_from(domain).to_string(),
|
||||||
|
description: str_from(description).to_string()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AppKitError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl error::Error for AppKitError {}
|
|
@ -1,69 +1,92 @@
|
||||||
//! A wrapper for `NSFileManager`, which is necessary for macOS/iOS (the sandbox makes things
|
//! A wrapper for `NSFileManager`, which is necessary for macOS/iOS (the sandbox makes things
|
||||||
//! tricky, and this transparently handles it for you).
|
//! tricky, and this transparently handles it for you).
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::error::Error;
|
||||||
use std::cell::RefCell;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use cocoa::base::{id, nil, NO};
|
use cocoa::base::{id, nil, NO};
|
||||||
use cocoa::foundation::NSUInteger;
|
use cocoa::foundation::{NSString, NSUInteger};
|
||||||
|
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
use objc::runtime::Object;
|
use objc::runtime::{BOOL, Object};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::error::AppKitError;
|
||||||
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
|
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
|
||||||
use crate::utils::str_from;
|
use crate::utils::str_from;
|
||||||
|
|
||||||
pub struct FileManagerInner {
|
pub struct FileManager {
|
||||||
pub manager: Id<Object>
|
pub manager: RwLock<Id<Object>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FileManagerInner {
|
impl Default for FileManager {
|
||||||
|
/// Returns a default file manager, which maps to the default system file manager. For common
|
||||||
|
/// and simple tasks, with no callbacks, you might want this.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
FileManagerInner {
|
FileManager {
|
||||||
manager: unsafe {
|
manager: RwLock::new(unsafe {
|
||||||
let manager: id = msg_send![class!(NSFileManager), defaultManager];
|
let manager: id = msg_send![class!(NSFileManager), defaultManager];
|
||||||
Id::from_ptr(manager)
|
Id::from_ptr(manager)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileManagerInner {
|
impl FileManager {
|
||||||
pub fn get_path(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result<String, Box<dyn std::error::Error>> {
|
/// Returns a new FileManager that opts in to delegate methods.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
FileManager {
|
||||||
|
manager: RwLock::new(unsafe {
|
||||||
|
let manager: id = msg_send![class!(NSFileManager), new];
|
||||||
|
Id::from_ptr(manager)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given a directory/domain combination, will attempt to get the directory that matches.
|
||||||
|
/// Returns a PathBuf that wraps the given location. If there's an error on the Objective-C
|
||||||
|
/// side, we attempt to catch it and bubble it up.
|
||||||
|
pub fn get_directory(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result<Url, Box<dyn Error>> {
|
||||||
let dir: NSUInteger = directory.into();
|
let dir: NSUInteger = directory.into();
|
||||||
let mask: NSUInteger = in_domain.into();
|
let mask: NSUInteger = in_domain.into();
|
||||||
|
|
||||||
unsafe {
|
let directory = unsafe {
|
||||||
let dir: id = msg_send![&*self.manager, URLForDirectory:dir
|
let manager = self.manager.read().unwrap();
|
||||||
|
let dir: id = msg_send![&**manager, URLForDirectory:dir
|
||||||
inDomain:mask
|
inDomain:mask
|
||||||
appropriateForURL:nil
|
appropriateForURL:nil
|
||||||
create:NO
|
create:NO
|
||||||
error:nil];
|
error:nil];
|
||||||
|
|
||||||
let s: id = msg_send![dir, path];
|
let s: id = msg_send![dir, absoluteString];
|
||||||
Ok(str_from(s).to_string())
|
str_from(s)
|
||||||
|
};
|
||||||
|
|
||||||
|
Url::parse(directory).map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Given two paths, moves file (`from`) to the location specified in `to`. This can result in
|
||||||
|
/// an error on the Objective-C side, which we attempt to handle and bubble up as a result if
|
||||||
|
/// so.
|
||||||
|
pub fn move_item(&self, from: Url, to: Url) -> Result<(), Box<dyn Error>> {
|
||||||
|
unsafe {
|
||||||
|
let s = NSString::alloc(nil).init_str(from.as_str());
|
||||||
|
let from_url: id = msg_send![class!(NSURL), URLWithString:s];
|
||||||
|
|
||||||
|
let s2 = NSString::alloc(nil).init_str(to.as_str());
|
||||||
|
let to_url: id = msg_send![class!(NSURL), URLWithString:s2];
|
||||||
|
|
||||||
|
// This should potentially be write(), but the backing class handles this logic
|
||||||
|
// already, so... going to leave it as read.
|
||||||
|
let manager = self.manager.read().unwrap();
|
||||||
|
|
||||||
|
let error: id = nil;
|
||||||
|
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
|
||||||
|
if result == NO {
|
||||||
|
return Err(AppKitError::new(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
Ok(())
|
||||||
pub struct FileManager(Rc<RefCell<FileManagerInner>>);
|
|
||||||
|
|
||||||
impl FileManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
FileManager(Rc::new(RefCell::new(FileManagerInner {
|
|
||||||
manager: unsafe {
|
|
||||||
let manager: id = msg_send![class!(NSFileManager), new];
|
|
||||||
Id::from_ptr(manager)
|
|
||||||
}
|
}
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_path(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let manager = self.0.borrow();
|
|
||||||
manager.get_path(directory, in_domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
//pub fn contents_of(directory: &str, properties: &[
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub mod color;
|
||||||
pub mod collection_view;
|
pub mod collection_view;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod dragdrop;
|
pub mod dragdrop;
|
||||||
|
pub mod error;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod filesystem;
|
pub mod filesystem;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
|
@ -40,6 +41,9 @@ pub mod view;
|
||||||
pub mod webview;
|
pub mod webview;
|
||||||
pub mod window;
|
pub mod window;
|
||||||
|
|
||||||
|
// We re-export these so that they can be used without increasing build times.
|
||||||
|
pub use url;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::app::{App, AppDelegate};
|
pub use crate::app::{App, AppDelegate};
|
||||||
|
|
||||||
|
@ -60,6 +64,6 @@ pub mod prelude {
|
||||||
pub use crate::view::{View, ViewController, ViewWrapper};
|
pub use crate::view::{View, ViewController, ViewWrapper};
|
||||||
|
|
||||||
pub use appkit_derive::{
|
pub use appkit_derive::{
|
||||||
WindowWrapper
|
WindowWrapper, ViewWrapper
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,18 @@
|
||||||
//! (think: drag and drop between applications). It exposes a Rust interface that tries to be
|
//! (think: drag and drop between applications). It exposes a Rust interface that tries to be
|
||||||
//! complete, but might not cover everything 100% right now - feel free to pull request.
|
//! complete, but might not cover everything 100% right now - feel free to pull request.
|
||||||
|
|
||||||
use cocoa::base::id;
|
use std::error::Error;
|
||||||
|
|
||||||
|
use cocoa::base::{id, nil};
|
||||||
|
use cocoa::foundation::{NSArray};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::pasteboard::types::PasteboardName;
|
use crate::error::AppKitError;
|
||||||
|
use crate::pasteboard::types::{PasteboardName, PasteboardType};
|
||||||
|
use crate::utils::str_from;
|
||||||
|
|
||||||
/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop.
|
/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop.
|
||||||
pub struct Pasteboard {
|
pub struct Pasteboard {
|
||||||
|
@ -32,7 +37,7 @@ impl Pasteboard {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should be pasteboardname enum!
|
/// Retrieves the system Pasteboard for the given name/type.
|
||||||
pub fn named(name: PasteboardName) -> Self {
|
pub fn named(name: PasteboardName) -> Self {
|
||||||
Pasteboard {
|
Pasteboard {
|
||||||
inner: unsafe {
|
inner: unsafe {
|
||||||
|
@ -64,4 +69,60 @@ impl Pasteboard {
|
||||||
let _: () = msg_send![&*self.inner, clearContents];
|
let _: () = msg_send![&*self.inner, clearContents];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Looks inside the pasteboard contents and extracts what FileURLs are there, if any.
|
||||||
|
pub fn get_file_urls(&self) -> Result<Vec<Url>, Box<dyn Error>> {
|
||||||
|
unsafe {
|
||||||
|
let mut i = 0;
|
||||||
|
|
||||||
|
let class: id = msg_send![class!(NSURL), class];
|
||||||
|
let classes: id = NSArray::arrayWithObjects(nil, &[class]);
|
||||||
|
let contents: id = msg_send![&*self.inner, readObjectsForClasses:classes options:nil];
|
||||||
|
|
||||||
|
// This can happen if the Pasteboard server has an error in returning items.
|
||||||
|
// In our case, we'll bubble up an error by checking the pasteboard.
|
||||||
|
if contents == nil {
|
||||||
|
// This error is not necessarily "correct", but in the event of an error in
|
||||||
|
// Pasteboard server retrieval I'm not sure where to check... and this stuff is
|
||||||
|
// kinda ancient and has conflicting docs in places. ;P
|
||||||
|
return Err(Box::new(AppKitError {
|
||||||
|
code: 666,
|
||||||
|
domain: "com.appkit-rs.pasteboard".to_string(),
|
||||||
|
description: "Pasteboard server returned no data.".to_string()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let count: usize = msg_send![contents, count];
|
||||||
|
let mut urls: Vec<Url> = Vec::with_capacity(count);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let nsurl: id = msg_send![contents, objectAtIndex:i];
|
||||||
|
let path: id = msg_send![nsurl, path];
|
||||||
|
let s = str_from(path);
|
||||||
|
urls.push(Url::parse(&format!("file://{}", s))?);
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
if i == count { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(urls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
/// Retrieves the pasteboard contents as a string. This can be `None` (`nil` on the Objective-C
|
||||||
|
/// side) if the pasteboard data doesn't match the requested type, so check accordingly.
|
||||||
|
///
|
||||||
|
/// Note: In macOS 10.6 and later, if the receiver contains multiple items that can provide string,
|
||||||
|
/// RTF, or RTFD data, the text data from each item is returned as a combined result separated by newlines.
|
||||||
|
/// This Rust wrapper is a quick pass, and could be improved. ;P
|
||||||
|
pub fn contents_for(&self, pasteboard_type: PasteboardType) -> Option<String> {
|
||||||
|
unsafe {
|
||||||
|
let contents: id = msg_send![&*self.inner, stringForType:pasteboard_type.to_nsstring()];
|
||||||
|
if contents != nil {
|
||||||
|
return Some(str_from(contents).to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,19 @@ use cocoa::foundation::NSString;
|
||||||
/// Constants for the standard system pasteboard names.
|
/// Constants for the standard system pasteboard names.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum PasteboardName {
|
pub enum PasteboardName {
|
||||||
|
/// The dragging/dropping pasteboard.
|
||||||
Drag,
|
Drag,
|
||||||
|
|
||||||
|
/// The find pasteboard.
|
||||||
Find,
|
Find,
|
||||||
|
|
||||||
|
/// The font pasteboard.
|
||||||
Font,
|
Font,
|
||||||
|
|
||||||
|
/// The general pasteboard.
|
||||||
General,
|
General,
|
||||||
|
|
||||||
|
/// The ruler pasteboard.
|
||||||
Ruler
|
Ruler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,11 +28,11 @@ impl PasteboardName {
|
||||||
pub fn to_nsstring(&self) -> id {
|
pub fn to_nsstring(&self) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
NSString::alloc(nil).init_str(match self {
|
NSString::alloc(nil).init_str(match self {
|
||||||
PasteboardName::Drag => "",
|
PasteboardName::Drag => "Apple CFPasteboard drag",
|
||||||
PasteboardName::Find => "",
|
PasteboardName::Find => "Apple CFPasteboard find",
|
||||||
PasteboardName::Font => "",
|
PasteboardName::Font => "Apple CFPasteboard font",
|
||||||
PasteboardName::General => "",
|
PasteboardName::General => "Apple CFPasteboard general",
|
||||||
PasteboardName::Ruler => ""
|
PasteboardName::Ruler => "Apple CFPasteboard ruler"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,10 @@ use cocoa::foundation::{NSUInteger};
|
||||||
use objc::declare::ClassDecl;
|
use objc::declare::ClassDecl;
|
||||||
use objc::runtime::{Class, Object, Sel, BOOL};
|
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
|
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
|
||||||
|
use crate::dragdrop::DragInfo;
|
||||||
use crate::view::traits::ViewController;
|
use crate::view::traits::ViewController;
|
||||||
|
|
||||||
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
||||||
|
@ -37,21 +39,25 @@ extern fn update_layer(this: &Object, _: Sel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, _: id) -> NSUInteger {
|
extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||||
let view = ptr as *const T;
|
let view = ptr as *const T;
|
||||||
(*view).dragging_entered().into()
|
(*view).dragging_entered(DragInfo {
|
||||||
|
info: Id::from_ptr(info)
|
||||||
|
}).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Sel, _: id) -> BOOL {
|
extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||||
let view = ptr as *const T;
|
let view = ptr as *const T;
|
||||||
|
|
||||||
match (*view).prepare_for_drag_operation() {
|
match (*view).prepare_for_drag_operation(DragInfo {
|
||||||
|
info: Id::from_ptr(info)
|
||||||
|
}) {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
}
|
}
|
||||||
|
@ -59,12 +65,14 @@ extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Se
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, _: id) -> BOOL {
|
extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||||
let view = ptr as *const T;
|
let view = ptr as *const T;
|
||||||
|
|
||||||
match (*view).perform_drag_operation() {
|
match (*view).perform_drag_operation(DragInfo {
|
||||||
|
info: Id::from_ptr(info)
|
||||||
|
}) {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
false => NO
|
||||||
}
|
}
|
||||||
|
@ -72,11 +80,25 @@ extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, _
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, _: id) {
|
extern fn conclude_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||||
let view = ptr as *const T;
|
let view = ptr as *const T;
|
||||||
(*view).dragging_exited();
|
|
||||||
|
(*view).conclude_drag_operation(DragInfo {
|
||||||
|
info: Id::from_ptr(info)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called when a drag/drop operation has entered this view.
|
||||||
|
extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, info: id) {
|
||||||
|
unsafe {
|
||||||
|
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||||
|
let view = ptr as *const T;
|
||||||
|
(*view).dragging_exited(DragInfo {
|
||||||
|
info: Id::from_ptr(info)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +125,7 @@ pub(crate) fn register_view_class<T: ViewController>() -> *const Class {
|
||||||
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
|
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!(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!(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!(draggingExited:), dragging_exited::<T> as extern fn (&mut Object, _, _));
|
||||||
|
|
||||||
VIEW_CLASS = decl.register();
|
VIEW_CLASS = decl.register();
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
|
|
||||||
use cocoa::base::{id, nil, YES, NO};
|
use cocoa::base::{id, NO};
|
||||||
use cocoa::foundation::{NSRect};
|
use cocoa::foundation::{NSRect};
|
||||||
|
|
||||||
use objc::declare::ClassDecl;
|
use objc::declare::ClassDecl;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use objc::runtime::Object;
|
||||||
|
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::dragdrop::DragOperation;
|
use crate::dragdrop::{DragInfo, DragOperation};
|
||||||
|
|
||||||
pub trait ViewWrapper {
|
pub trait ViewWrapper {
|
||||||
fn get_handle(&self) -> Option<ShareId<Object>>;
|
fn get_handle(&self) -> Option<ShareId<Object>>;
|
||||||
|
@ -13,8 +13,19 @@ pub trait ViewWrapper {
|
||||||
pub trait ViewController {
|
pub trait ViewController {
|
||||||
fn did_load(&self);
|
fn did_load(&self);
|
||||||
|
|
||||||
fn dragging_entered(&self) -> DragOperation { DragOperation::None }
|
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
|
||||||
fn prepare_for_drag_operation(&self) -> bool { false }
|
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }
|
||||||
fn perform_drag_operation(&self) -> bool { false }
|
|
||||||
fn dragging_exited(&self) {}
|
/// 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 }
|
||||||
|
|
||||||
|
/// 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 }
|
||||||
|
|
||||||
|
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up.
|
||||||
|
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) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use cocoa::base::{id, nil, YES, NO};
|
use cocoa::base::{id, nil, YES};
|
||||||
use cocoa::foundation::NSArray;
|
use cocoa::foundation::NSArray;
|
||||||
|
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
@ -86,3 +86,9 @@ impl View {
|
||||||
view.set_background_color(color);
|
view.set_background_color(color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for View {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "View ({:p})", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ impl Default for WindowConfig {
|
||||||
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(800., 600.));
|
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(800., 600.));
|
||||||
|
|
||||||
let style = WindowStyle::Resizable | WindowStyle::Miniaturizable | WindowStyle::UnifiedTitleAndToolbar |
|
let style = WindowStyle::Resizable | WindowStyle::Miniaturizable | WindowStyle::UnifiedTitleAndToolbar |
|
||||||
WindowStyle::Closable | WindowStyle::Titled;
|
WindowStyle::Closable | WindowStyle::Titled | WindowStyle::FullSizeContentView;
|
||||||
|
|
||||||
let alloc: id = msg_send![class!(NSWindow), alloc];
|
let alloc: id = msg_send![class!(NSWindow), alloc];
|
||||||
let window: id = msg_send![alloc, initWithContentRect:dimensions styleMask:style backing:2 as NSUInteger defer:YES];
|
let window: id = msg_send![alloc, initWithContentRect:dimensions styleMask:style backing:2 as NSUInteger defer:YES];
|
||||||
|
|
|
@ -7,13 +7,13 @@ use crate::proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{DeriveInput, parse_macro_input};
|
use syn::{DeriveInput, parse_macro_input};
|
||||||
|
|
||||||
/// Derivces an `appkit::prelude::WinWrapper` block, which implements forwarding methods for things
|
/// Derivces an `appkit::prelude::WindowWrapper` block, which implements forwarding methods for things
|
||||||
/// like setting the window title, or showing and closing it. It currently expects that the wrapped
|
/// like setting the window title, or showing and closing it. It currently expects that the wrapped
|
||||||
/// struct has `window` as the field holding the `Window` from `appkit-rs`.
|
/// struct has `window` as the field holding the `Window` from `appkit-rs`.
|
||||||
///
|
///
|
||||||
/// Note that this expects that pointers to Window(s) should not move once created.
|
/// Note that this expects that pointers to Window(s) should not move once created.
|
||||||
#[proc_macro_derive(WindowWrapper)]
|
#[proc_macro_derive(WindowWrapper)]
|
||||||
pub fn impl_window_controller(input: TokenStream) -> TokenStream {
|
pub fn impl_window_wrapper(input: TokenStream) -> TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
let name = &input.ident;
|
let name = &input.ident;
|
||||||
|
@ -30,3 +30,24 @@ pub fn impl_window_controller(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
TokenStream::from(expanded)
|
TokenStream::from(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives an `appkit::prelude::ViewWrapper` block, which implements some necessary bits and
|
||||||
|
/// pieces for View handling.
|
||||||
|
#[proc_macro_derive(ViewWrapper)]
|
||||||
|
pub fn impl_view_wrapper(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
|
||||||
|
let name = &input.ident;
|
||||||
|
let generics = input.generics;
|
||||||
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl #impl_generics appkit::prelude::ViewWrapper for #name #ty_generics #where_clause {
|
||||||
|
fn get_handle(&self) -> Option<appkit::ShareId<appkit::Object>> {
|
||||||
|
self.view.get_handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TokenStream::from(expanded)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue