Very rough experimental support for iOS13+ Scenes. Not ready for use or comment yet.
This commit is contained in:
parent
0c604c2e84
commit
4ff69c008a
|
@ -14,6 +14,7 @@ block = "0.1.6"
|
||||||
core-foundation = { version = "0.7", features = ["with-chrono"] }
|
core-foundation = { version = "0.7", features = ["with-chrono"] }
|
||||||
core-graphics = "0.19.0"
|
core-graphics = "0.19.0"
|
||||||
dispatch = "0.2.0"
|
dispatch = "0.2.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
objc_id = "0.1.1"
|
objc_id = "0.1.1"
|
||||||
|
|
25
src/error.rs
25
src/error.rs
|
@ -1,5 +1,6 @@
|
||||||
//! A wrapper for `NSError`, which can be (and is) bubbled up for certain calls in this library. It
|
//! A wrapper for `NSError`.
|
||||||
//! attempts to be thread safe where possible, and extract the "default" usable information out of
|
//!
|
||||||
|
//! 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,
|
//! an `NSError`. This might not be what you need, though, so if it's missing something... well,
|
||||||
//! it's up for discussion.
|
//! it's up for discussion.
|
||||||
|
|
||||||
|
@ -14,13 +15,18 @@ use crate::foundation::{id, nil, NSInteger, NSString};
|
||||||
/// allocates `String` instances when theoretically it could be avoided, and we might be erasing
|
/// allocates `String` instances when theoretically it could be avoided, and we might be erasing
|
||||||
/// certain parts of the `NSError` object that are useful.
|
/// certain parts of the `NSError` object that are useful.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AppKitError {
|
pub struct Error {
|
||||||
|
/// Represents the code. Some of these can be... archaic.
|
||||||
pub code: usize,
|
pub code: usize,
|
||||||
|
|
||||||
|
/// Represents the domain of the error.
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
|
|
||||||
|
/// Maps over to `[NSError localizedDescription]`.
|
||||||
pub description: String
|
pub description: String
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppKitError {
|
impl Error {
|
||||||
/// Given an `NSError` (i.e, an id reference) we'll pull out the relevant information and
|
/// 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,
|
/// configure this. We pull out the information as it makes the error thread safe this way,
|
||||||
/// which is... easier, in some cases.
|
/// which is... easier, in some cases.
|
||||||
|
@ -33,19 +39,20 @@ impl AppKitError {
|
||||||
(code, domain, description)
|
(code, domain, description)
|
||||||
};
|
};
|
||||||
|
|
||||||
AppKitError {
|
Error {
|
||||||
code: code,
|
code: code,
|
||||||
domain: domain.to_str().to_string(),
|
domain: domain.to_str().to_string(),
|
||||||
description: description.to_str().to_string()
|
description: description.to_str().to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a boxed `Error`.
|
||||||
pub fn boxed(error: id) -> Box<Self> {
|
pub fn boxed(error: id) -> Box<Self> {
|
||||||
Box::new(AppKitError::new(error))
|
Box::new(Error::new(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used for cases where we need to return an `NSError` back to the system (e.g, top-level
|
/// Used for cases where we need to return an `NSError` back to the system (e.g, top-level
|
||||||
/// error handling). We just create a new `NSError` so the `AppKitError` crate can be mostly
|
/// error handling). We just create a new `NSError` so the `Error` crate can be mostly
|
||||||
/// thread safe.
|
/// thread safe.
|
||||||
pub fn into_nserror(self) -> id {
|
pub fn into_nserror(self) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -56,10 +63,10 @@ impl AppKitError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for AppKitError {
|
impl fmt::Display for Error {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.description)
|
write!(f, "{}", self.description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for AppKitError {}
|
impl error::Error for Error {}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, NO, NSString, NSUInteger};
|
use crate::foundation::{id, nil, NO, NSString, NSUInteger};
|
||||||
use crate::error::AppKitError;
|
use crate::error::{Error as AppKitError};
|
||||||
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
|
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
|
||||||
|
|
||||||
pub struct FileManager {
|
pub struct FileManager {
|
||||||
|
|
|
@ -43,3 +43,14 @@ impl From<Rect> for CGRect {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<CGRect> for Rect {
|
||||||
|
fn from(rect: CGRect) -> Rect {
|
||||||
|
Rect {
|
||||||
|
top: rect.origin.y as f64,
|
||||||
|
left: rect.origin.x as f64,
|
||||||
|
width: rect.size.width as f64,
|
||||||
|
height: rect.size.height as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
//! creates a custom `UIApplication` subclass that currently does nothing; this is meant as a hook
|
//! creates a custom `UIApplication` subclass that currently does nothing; this is meant as a hook
|
||||||
//! for potential future use.
|
//! for potential future use.
|
||||||
|
|
||||||
use std::ffi::c_void;
|
//use std::ffi::c_void;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
use std::unreachable;
|
//use std::unreachable;
|
||||||
|
|
||||||
use block::Block;
|
//use block::Block;
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::declare::ClassDecl;
|
use objc::declare::ClassDecl;
|
||||||
|
@ -14,10 +14,12 @@ use objc::runtime::{Class, Object, Sel};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::error::AppKitError;
|
//use crate::error::Error;
|
||||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
||||||
|
//use crate::user_activity::UserActivity;
|
||||||
|
|
||||||
use crate::ios::app::{AppDelegate, APP_DELEGATE};
|
use crate::ios::app::{AppDelegate, APP_DELEGATE};
|
||||||
use crate::user_activity::UserActivity;
|
use crate::ios::scene::{SceneConfig, SceneConnectionOptions, SceneSession};
|
||||||
|
|
||||||
#[cfg(feature = "cloudkit")]
|
#[cfg(feature = "cloudkit")]
|
||||||
use crate::cloudkit::share::CKShareMetaData;
|
use crate::cloudkit::share::CKShareMetaData;
|
||||||
|
@ -34,11 +36,17 @@ fn app<T>(this: &Object) -> &T {
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
||||||
extern fn did_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id, _: id) -> BOOL {
|
extern fn did_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id, _: id) -> BOOL {
|
||||||
println!("HMMMM");
|
|
||||||
app::<T>(this).did_finish_launching();
|
app::<T>(this).did_finish_launching();
|
||||||
YES
|
YES
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern fn configuration_for_scene_session<T: AppDelegate>(this: &Object, _: Sel, _: id, session: id, opts: id) -> id {
|
||||||
|
app::<T>(this).config_for_scene_session(
|
||||||
|
SceneSession::with(session),
|
||||||
|
SceneConnectionOptions::with(opts)
|
||||||
|
).into_inner()
|
||||||
|
}
|
||||||
|
|
||||||
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
|
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
|
||||||
/// pointers we need to have.
|
/// pointers we need to have.
|
||||||
pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
|
pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
|
||||||
|
@ -50,7 +58,20 @@ pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
|
||||||
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();
|
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();
|
||||||
|
|
||||||
// Launching Applications
|
// Launching Applications
|
||||||
decl.add_method(sel!(application:didFinishLaunchingWithOptions:), did_finish_launching::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
decl.add_method(
|
||||||
|
sel!(application:didFinishLaunchingWithOptions:),
|
||||||
|
did_finish_launching::<T> as extern fn(&Object, _, _, id) -> BOOL
|
||||||
|
);
|
||||||
|
|
||||||
|
// Scenes
|
||||||
|
decl.add_method(
|
||||||
|
sel!(application:configurationForConnectingSceneSession:options:),
|
||||||
|
configuration_for_scene_session::<T> as extern fn(&Object, _, _, id, id) -> id
|
||||||
|
);
|
||||||
|
/*decl.add_method(
|
||||||
|
sel!(application:didDiscardSceneSessions:),
|
||||||
|
did_discard_scene_sessions::<T> as extern fn(&Object, _, _, id)
|
||||||
|
);*/
|
||||||
|
|
||||||
DELEGATE_CLASS = decl.register();
|
DELEGATE_CLASS = decl.register();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
//! Various types used at the AppController level.
|
//! Various types used at the AppController level.
|
||||||
|
|
||||||
use crate::foundation::NSUInteger;
|
//use crate::foundation::NSUInteger;
|
||||||
|
|
||||||
|
|
|
@ -37,11 +37,11 @@
|
||||||
use libc::{c_char, c_int};
|
use libc::{c_char, c_int};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
|
||||||
use objc_id::Id;
|
|
||||||
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 crate::foundation::{id, nil, YES, NO, NSString, NSUInteger, AutoReleasePool};
|
use crate::foundation::{id, nil, YES, NO, NSString, NSUInteger, AutoReleasePool};
|
||||||
|
use crate::ios::scene::{WindowSceneDelegate, register_window_scene_delegate_class};
|
||||||
use crate::notification_center::Dispatcher;
|
use crate::notification_center::Dispatcher;
|
||||||
use crate::utils::activate_cocoa_multithreading;
|
use crate::utils::activate_cocoa_multithreading;
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ mod traits;
|
||||||
pub use traits::AppDelegate;
|
pub use traits::AppDelegate;
|
||||||
|
|
||||||
pub(crate) static mut APP_DELEGATE: usize = 0;
|
pub(crate) static mut APP_DELEGATE: usize = 0;
|
||||||
|
pub(crate) static mut SCENE_DELEGATE_VENDOR: usize = 0;
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
/// Required for iOS applications to initialize.
|
/// Required for iOS applications to initialize.
|
||||||
|
@ -83,17 +84,64 @@ fn shared_application<F: Fn(id)>(handler: F) {
|
||||||
/// - It injects an `NSObject` subclass to act as a delegate for lifecycle events.
|
/// - It injects an `NSObject` subclass to act as a delegate for lifecycle events.
|
||||||
/// - It ensures that Cocoa, where appropriate, is operating in multi-threaded mode so POSIX
|
/// - It ensures that Cocoa, where appropriate, is operating in multi-threaded mode so POSIX
|
||||||
/// threads work as intended.
|
/// threads work as intended.
|
||||||
///
|
pub struct App<
|
||||||
/// This also enables support for dispatching a message, `M`. Your `AppDelegate` can optionally
|
T = (),
|
||||||
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
|
W = (),
|
||||||
/// application.
|
F = (),
|
||||||
pub struct App<T = (), M = ()> {
|
> {
|
||||||
pub delegate: Box<T>,
|
pub delegate: Box<T>,
|
||||||
|
pub vendor: Box<F>,
|
||||||
pub pool: AutoReleasePool,
|
pub pool: AutoReleasePool,
|
||||||
_t: std::marker::PhantomData<M>
|
_w: std::marker::PhantomData<W>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> App<T> {
|
impl<T, W, F> App<T, W, F>
|
||||||
|
where
|
||||||
|
T: AppDelegate + 'static,
|
||||||
|
W: WindowSceneDelegate,
|
||||||
|
F: Fn() -> Box<W>
|
||||||
|
{
|
||||||
|
/// iOS manages creating a new Application (`UIApplication`) differently than you'd expect if
|
||||||
|
/// you were looking at the macOS side of things.
|
||||||
|
///
|
||||||
|
/// In this case, we're primarily concerned with shoving our `AppDelegate` to a place we can
|
||||||
|
/// retrieve it later on. While this is unsafe behavior, it's ultimately no different than
|
||||||
|
/// shoving the pointer onto the delegate like we do on the macOS side of things.
|
||||||
|
///
|
||||||
|
/// Note that this pattern is only fine here due to the fact that there can only be one
|
||||||
|
/// AppDelegate at a time.
|
||||||
|
///
|
||||||
|
/// This also handles ensuring that our subclasses exist in the Objective-C runtime *before*
|
||||||
|
/// `UIApplicationMain` is called.
|
||||||
|
pub fn new(delegate: T, scene_delegate_vendor: F) -> Self {
|
||||||
|
activate_cocoa_multithreading();
|
||||||
|
|
||||||
|
let pool = AutoReleasePool::new();
|
||||||
|
let cls = register_app_class();
|
||||||
|
let dl = register_app_delegate_class::<T>();
|
||||||
|
let w = register_window_scene_delegate_class::<W, F>();
|
||||||
|
|
||||||
|
// This probably needs to be Arc<Mutex<>>'d at some point, but this is still exploratory.
|
||||||
|
let app_delegate = Box::new(delegate);
|
||||||
|
let vendor = Box::new(scene_delegate_vendor);
|
||||||
|
unsafe {
|
||||||
|
let delegate_ptr: *const T = &*app_delegate;
|
||||||
|
APP_DELEGATE = delegate_ptr as usize;
|
||||||
|
|
||||||
|
let scene_delegate_vendor_ptr: *const F = &*vendor;
|
||||||
|
SCENE_DELEGATE_VENDOR = scene_delegate_vendor_ptr as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
App {
|
||||||
|
delegate: app_delegate,
|
||||||
|
vendor: vendor,
|
||||||
|
pool: pool,
|
||||||
|
_w: std::marker::PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, W, F> App<T, W, F> {
|
||||||
/// Handles calling through to `UIApplicationMain()`, ensuring that it's using our custom
|
/// Handles calling through to `UIApplicationMain()`, ensuring that it's using our custom
|
||||||
/// `UIApplication` and `UIApplicationDelegate` classes.
|
/// `UIApplication` and `UIApplicationDelegate` classes.
|
||||||
pub fn run(&self) {
|
pub fn run(&self) {
|
||||||
|
@ -111,41 +159,3 @@ impl<T> App<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> App<T> where T: AppDelegate + 'static {
|
|
||||||
/// iOS manages creating a new Application (`UIApplication`) differently than you'd expect if
|
|
||||||
/// you were looking at the macOS side of things.
|
|
||||||
///
|
|
||||||
/// In this case, we're primarily concerned with shoving our `AppDelegate` to a place we can
|
|
||||||
/// retrieve it later on. While this is unsafe behavior, it's ultimately no different than
|
|
||||||
/// shoving the pointer onto the delegate like we do on the macOS side of things.
|
|
||||||
///
|
|
||||||
/// This also handles ensuring that our subclasses exist in the Objective-C runtime *before*
|
|
||||||
/// `UIApplicationMain` is called.
|
|
||||||
pub fn new(delegate: T) -> Self {
|
|
||||||
activate_cocoa_multithreading();
|
|
||||||
|
|
||||||
let pool = AutoReleasePool::new();
|
|
||||||
let cls = register_app_class();
|
|
||||||
let dl = register_app_delegate_class::<T>();
|
|
||||||
let app_delegate = Box::new(delegate);
|
|
||||||
let delegate_ptr: *const T = &*app_delegate;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
APP_DELEGATE = delegate_ptr as usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
App {
|
|
||||||
delegate: app_delegate,
|
|
||||||
pool: pool,
|
|
||||||
_t: std::marker::PhantomData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a hack and should be replaced with an actual messaging pipeline at some point. :)
|
|
||||||
impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher<Message = M> {
|
|
||||||
/// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate,
|
|
||||||
/// and passing back through there. All messages are currently dispatched on the main thread.
|
|
||||||
pub fn dispatch(message: M) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
//! Traits that an implementing application can conform to. These aim to wrap the general
|
//! Traits that an implementing application can conform to. These aim to wrap the general
|
||||||
//! lifecycles across macOS/iOS/etc, while still conforming to a Rust-ish approach.
|
//! lifecycles across macOS/iOS/etc, while still conforming to a Rust-ish approach.
|
||||||
|
|
||||||
use url::Url;
|
//use url::Url;
|
||||||
|
|
||||||
use crate::error::AppKitError;
|
//use crate::error::Error;
|
||||||
use crate::user_activity::UserActivity;
|
//use crate::user_activity::UserActivity;
|
||||||
|
use crate::ios::scene::{SceneConfig, SceneConnectionOptions, SceneSession};
|
||||||
|
|
||||||
#[cfg(feature = "cloudkit")]
|
#[cfg(feature = "cloudkit")]
|
||||||
use crate::cloudkit::share::CKShareMetaData;
|
use crate::cloudkit::share::CKShareMetaData;
|
||||||
|
@ -18,4 +19,6 @@ pub trait AppDelegate {
|
||||||
/// events in this framework, you don't get a reference to an app here - if you need to call
|
/// events in this framework, you don't get a reference to an app here - if you need to call
|
||||||
/// through to your shared application, then used the `App::shared()` call.
|
/// through to your shared application, then used the `App::shared()` call.
|
||||||
fn did_finish_launching(&self) {}
|
fn did_finish_launching(&self) {}
|
||||||
|
|
||||||
|
fn config_for_scene_session(&self, session: SceneSession, options: SceneConnectionOptions) -> SceneConfig;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,9 @@
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
|
|
||||||
|
mod scene;
|
||||||
|
pub use scene::*;
|
||||||
|
|
||||||
|
mod window;
|
||||||
|
pub use window::*;
|
||||||
|
|
36
src/ios/scene/config.rs
Normal file
36
src/ios/scene/config.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, NSString};
|
||||||
|
use crate::ios::scene::SessionRole;
|
||||||
|
|
||||||
|
/// A wrapper for UISceneConfiguration.
|
||||||
|
///
|
||||||
|
/// Due to the way we have to implement this, you likely never need to touch this.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SceneConfig(Id<Object>);
|
||||||
|
|
||||||
|
impl SceneConfig {
|
||||||
|
/// Creates a new `UISceneConfiguration` with the specified name and session role, retains it,
|
||||||
|
/// and returns it.
|
||||||
|
pub fn new(name: &str, role: SessionRole) -> Self {
|
||||||
|
SceneConfig(unsafe {
|
||||||
|
let name = NSString::new(name);
|
||||||
|
let role = NSString::from(role);
|
||||||
|
|
||||||
|
let cls = class!(UISceneConfiguration);
|
||||||
|
let config: id = msg_send![cls, configurationWithName:name sessionRole:role];
|
||||||
|
|
||||||
|
let _: () = msg_send![config, setSceneClass:class!(UIWindowScene)];
|
||||||
|
let _: () = msg_send![config, setDelegateClass:class!(RSTWindowSceneDelegate)];
|
||||||
|
|
||||||
|
Id::from_ptr(config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes and returns the underlying `UISceneConfiguration`.
|
||||||
|
pub fn into_inner(mut self) -> id {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
99
src/ios/scene/delegate.rs
Normal file
99
src/ios/scene/delegate.rs
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
//! This module implements forwarding methods for standard `UIApplicationDelegate` calls. It also
|
||||||
|
//! creates a custom `UIApplication` subclass that currently does nothing; this is meant as a hook
|
||||||
|
//! for potential future use.
|
||||||
|
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::sync::Once;
|
||||||
|
use std::unreachable;
|
||||||
|
|
||||||
|
use block::Block;
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::declare::ClassDecl;
|
||||||
|
use objc::runtime::{Class, Object, Sel};
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
||||||
|
use crate::user_activity::UserActivity;
|
||||||
|
use crate::utils::load;
|
||||||
|
|
||||||
|
use crate::ios::app::{SCENE_DELEGATE_VENDOR};
|
||||||
|
use crate::ios::scene::{Scene, SceneConfig, SceneConnectionOptions, SceneSession, WindowSceneDelegate};
|
||||||
|
|
||||||
|
#[cfg(feature = "cloudkit")]
|
||||||
|
use crate::cloudkit::share::CKShareMetaData;
|
||||||
|
|
||||||
|
pub(crate) static WINDOW_SCENE_PTR: &str = "rstWindowSceneDelegatePtr";
|
||||||
|
|
||||||
|
///
|
||||||
|
extern fn init<
|
||||||
|
T: WindowSceneDelegate,
|
||||||
|
F: Fn() -> Box<T>
|
||||||
|
>(this: &mut Object, _: Sel) -> id {
|
||||||
|
let x = unsafe {
|
||||||
|
*this = msg_send![super(this, class!(UIResponder)), init];
|
||||||
|
|
||||||
|
let scene_delegate_vendor = SCENE_DELEGATE_VENDOR as *const F;
|
||||||
|
let factory: &F = &*scene_delegate_vendor;
|
||||||
|
let scene_delegate = factory();
|
||||||
|
let scene_delegate_ptr = Box::into_raw(scene_delegate);
|
||||||
|
println!("scene ptr: {:p}", scene_delegate_ptr);
|
||||||
|
this.set_ivar(WINDOW_SCENE_PTR, scene_delegate_ptr as usize);
|
||||||
|
|
||||||
|
this
|
||||||
|
};
|
||||||
|
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
extern fn scene_will_connect_to_session_with_options<
|
||||||
|
T: WindowSceneDelegate
|
||||||
|
>(this: &Object, _: Sel, scene: id, session: id, options: id) {
|
||||||
|
let delegate = load::<T>(this, WINDOW_SCENE_PTR);
|
||||||
|
|
||||||
|
delegate.will_connect(
|
||||||
|
Scene::with(scene),
|
||||||
|
SceneSession::with(session),
|
||||||
|
SceneConnectionOptions::with(options)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
|
||||||
|
/// pointers we need to have.
|
||||||
|
pub(crate) fn register_window_scene_delegate_class<
|
||||||
|
T: WindowSceneDelegate,
|
||||||
|
F: Fn() -> Box<T>
|
||||||
|
>() -> *const Class {
|
||||||
|
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
use objc::runtime::{Protocol, class_addProtocol};
|
||||||
|
INIT.call_once(|| unsafe {
|
||||||
|
let superclass = class!(UIResponder);
|
||||||
|
let mut decl = ClassDecl::new("RSTWindowSceneDelegate", superclass).unwrap();
|
||||||
|
|
||||||
|
let p = Protocol::get("UIWindowSceneDelegate").unwrap();
|
||||||
|
|
||||||
|
// A spot to hold a pointer to
|
||||||
|
decl.add_ivar::<usize>(WINDOW_SCENE_PTR);
|
||||||
|
decl.add_protocol(p);
|
||||||
|
|
||||||
|
// Override the `init` call to handle creating and attaching a WindowSceneDelegate.
|
||||||
|
decl.add_method(sel!(init), init::<T, F> as extern fn(&mut Object, _) -> id);
|
||||||
|
|
||||||
|
// UIWindowSceneDelegate API
|
||||||
|
decl.add_method(
|
||||||
|
sel!(scene:willConnectToSession:options:),
|
||||||
|
scene_will_connect_to_session_with_options::<T> as extern fn(&Object, _, _, _, _)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Launching Applications
|
||||||
|
DELEGATE_CLASS = decl.register();
|
||||||
|
});
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
DELEGATE_CLASS
|
||||||
|
}
|
||||||
|
}
|
34
src/ios/scene/enums.rs
Normal file
34
src/ios/scene/enums.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use crate::foundation::{id, NSString};
|
||||||
|
|
||||||
|
/// Represents the types of sessions a Scene is for.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum SessionRole {
|
||||||
|
/// The scene displays interactive windows on the device's main screen.
|
||||||
|
Application,
|
||||||
|
|
||||||
|
/// Noninteractive windows on an external display.
|
||||||
|
ExternalDisplay,
|
||||||
|
|
||||||
|
// Interactive content on a CarPlay screen.
|
||||||
|
//CarPlayApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SessionRole> for NSString {
|
||||||
|
fn from(role: SessionRole) -> Self {
|
||||||
|
NSString::new(match role {
|
||||||
|
SessionRole::Application => "UIWindowSceneSessionRoleApplication",
|
||||||
|
SessionRole::ExternalDisplay => "UIWindowSceneSessionRoleExternalDisplay",
|
||||||
|
//SessionRole::CarPlayApplication => ""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<NSString> for SessionRole {
|
||||||
|
fn from(value: NSString) -> Self {
|
||||||
|
match value.to_str() {
|
||||||
|
"UIWindowSceneSessionRoleApplication" => SessionRole::Application,
|
||||||
|
"UIWindowSceneSessionRoleExternalDisplay" => SessionRole::ExternalDisplay,
|
||||||
|
_ => SessionRole::Application
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/ios/scene/mod.rs
Normal file
56
src/ios/scene/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
//! A wrapper around various pieces of the iOS13+ UIScene API.
|
||||||
|
//!
|
||||||
|
//! This is required for things like having multiple instances of your app in the app switcher on
|
||||||
|
//! iPad. In general, you probably won't need to tweak this though.
|
||||||
|
|
||||||
|
use core_graphics::geometry::CGRect;
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
use crate::geometry::Rect;
|
||||||
|
|
||||||
|
mod delegate;
|
||||||
|
pub(crate) use delegate::*;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
pub use config::SceneConfig;
|
||||||
|
|
||||||
|
mod enums;
|
||||||
|
pub use enums::*;
|
||||||
|
|
||||||
|
mod traits;
|
||||||
|
pub use traits::*;
|
||||||
|
|
||||||
|
mod options;
|
||||||
|
pub use options::*;
|
||||||
|
|
||||||
|
mod session;
|
||||||
|
pub use session::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Scene(pub Id<Object>);
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
pub fn with(scene: id) -> Self {
|
||||||
|
Scene(unsafe {
|
||||||
|
Id::from_ptr(scene)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is temporary - I'm not wrapping `coordinateSpace` until I'm happy with the ergonomics
|
||||||
|
// of everything.
|
||||||
|
pub fn get_bounds(&self) -> Rect {
|
||||||
|
unsafe {
|
||||||
|
let coordinate_space: id = msg_send![&*self.0, coordinateSpace];
|
||||||
|
let rect: CGRect = msg_send![coordinate_space, bounds];
|
||||||
|
rect
|
||||||
|
}.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(mut self) -> id {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
24
src/ios/scene/options.rs
Normal file
24
src/ios/scene/options.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::{id, NSString};
|
||||||
|
|
||||||
|
/// A wrapper for UISceneConfiguration.
|
||||||
|
///
|
||||||
|
/// Due to the way we have to implement this, you likely never need to touch this.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SceneConnectionOptions(Id<Object>);
|
||||||
|
|
||||||
|
impl SceneConnectionOptions {
|
||||||
|
pub fn with(opts: id) -> Self {
|
||||||
|
SceneConnectionOptions(unsafe {
|
||||||
|
Id::from_ptr(opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes and returns the underlying `UISceneConfiguration`.
|
||||||
|
pub fn into_inner(mut self) -> id {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
23
src/ios/scene/session.rs
Normal file
23
src/ios/scene/session.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
use objc_id::Id;
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::{id, NSString};
|
||||||
|
use crate::ios::scene::enums::SessionRole;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SceneSession(pub Id<Object>);
|
||||||
|
|
||||||
|
impl SceneSession {
|
||||||
|
pub fn with(session: id) -> Self {
|
||||||
|
SceneSession(unsafe {
|
||||||
|
Id::from_ptr(session)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn role(&self) -> SessionRole {
|
||||||
|
NSString::wrap(unsafe {
|
||||||
|
msg_send![&*self.0, role]
|
||||||
|
}).into()
|
||||||
|
}
|
||||||
|
}
|
10
src/ios/scene/traits.rs
Normal file
10
src/ios/scene/traits.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use crate::ios::scene::{Scene, SceneSession, SceneConnectionOptions};
|
||||||
|
|
||||||
|
pub trait WindowSceneDelegate {
|
||||||
|
fn will_connect(
|
||||||
|
&self,
|
||||||
|
scene: Scene,
|
||||||
|
session: SceneSession,
|
||||||
|
options: SceneConnectionOptions
|
||||||
|
);
|
||||||
|
}
|
44
src/ios/window/mod.rs
Normal file
44
src/ios/window/mod.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
use core_graphics::geometry::CGRect;
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
use objc::runtime::Object;
|
||||||
|
use objc_id::Id;
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
use crate::geometry::Rect;
|
||||||
|
use crate::ios::Scene;
|
||||||
|
use crate::utils::Controller;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Window(pub Id<Object>);
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(frame: Rect) -> Self {
|
||||||
|
Window(unsafe {
|
||||||
|
let rect: CGRect = frame.into();
|
||||||
|
let alloc: id = msg_send![class!(UIWindow), alloc];
|
||||||
|
Id::from_ptr(msg_send![alloc, initWithFrame:rect])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_window_scene(&mut self, scene: Scene) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, setWindowScene:scene.into_inner()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_root_view_controller<VC: Controller + 'static>(&self, controller: &VC) {
|
||||||
|
let backing_node = controller.get_backing_node();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, setRootViewController:&*backing_node];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(&self) {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, makeKeyAndVisible];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//#![deny(missing_docs)]
|
//#![deny(missing_docs)]
|
||||||
//#![deny(missing_debug_implementations)]
|
//#![deny(missing_debug_implementations)]
|
||||||
|
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
|
||||||
|
|
||||||
// Copyright 2019+, the Cacao developers.
|
// Copyright 2019+, the Cacao developers.
|
||||||
// See the COPYRIGHT file at the top-level directory of this distribution.
|
// See the COPYRIGHT file at the top-level directory of this distribution.
|
||||||
|
|
|
@ -14,7 +14,7 @@ use objc::runtime::{Class, Object, Sel};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::error::AppKitError;
|
use crate::error::Error;
|
||||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
||||||
use crate::macos::app::{APP_PTR, AppDelegate};
|
use crate::macos::app::{APP_PTR, AppDelegate};
|
||||||
use crate::macos::printing::PrintSettings;
|
use crate::macos::printing::PrintSettings;
|
||||||
|
@ -126,7 +126,7 @@ extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
|
||||||
|
|
||||||
/// Fires when the application delegate receives a `application:willPresentError:` notification.
|
/// Fires when the application delegate receives a `application:willPresentError:` notification.
|
||||||
extern fn will_present_error<T: AppDelegate>(this: &Object, _: Sel, _: id, error: id) -> id {
|
extern fn will_present_error<T: AppDelegate>(this: &Object, _: Sel, _: id, error: id) -> id {
|
||||||
let error = AppKitError::new(error);
|
let error = Error::new(error);
|
||||||
app::<T>(this).will_present_error(error).into_nserror()
|
app::<T>(this).will_present_error(error).into_nserror()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ extern fn continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, a
|
||||||
extern fn failed_to_continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
extern fn failed_to_continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
||||||
app::<T>(this).failed_to_continue_user_activity(
|
app::<T>(this).failed_to_continue_user_activity(
|
||||||
NSString::wrap(activity_type).to_str(),
|
NSString::wrap(activity_type).to_str(),
|
||||||
AppKitError::new(error)
|
Error::new(error)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ extern fn registered_for_remote_notifications<T: AppDelegate>(_this: &Object, _:
|
||||||
|
|
||||||
/// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message.
|
/// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message.
|
||||||
extern fn failed_to_register_for_remote_notifications<T: AppDelegate>(this: &Object, _: Sel, _: id, error: id) {
|
extern fn failed_to_register_for_remote_notifications<T: AppDelegate>(this: &Object, _: Sel, _: id, error: id) {
|
||||||
app::<T>(this).failed_to_register_for_remote_notifications(AppKitError::new(error));
|
app::<T>(this).failed_to_register_for_remote_notifications(Error::new(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives a `application:didReceiveRemoteNotification:` message.
|
/// Fires when the application receives a `application:didReceiveRemoteNotification:` message.
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::error::AppKitError;
|
use crate::error::Error;
|
||||||
use crate::user_activity::UserActivity;
|
use crate::user_activity::UserActivity;
|
||||||
|
|
||||||
use crate::macos::app::enums::TerminateResponse;
|
use crate::macos::app::enums::TerminateResponse;
|
||||||
|
@ -43,7 +43,7 @@ pub trait AppDelegate {
|
||||||
fn continue_user_activity<F: Fn()>(&self, _activity: UserActivity, _restoration_handler: F) -> bool { false }
|
fn continue_user_activity<F: Fn()>(&self, _activity: UserActivity, _restoration_handler: F) -> bool { false }
|
||||||
|
|
||||||
/// Fired when the activity could not be continued.
|
/// Fired when the activity could not be continued.
|
||||||
fn failed_to_continue_user_activity(&self, _activity_type: &str, _error: AppKitError) {}
|
fn failed_to_continue_user_activity(&self, _activity_type: &str, _error: Error) {}
|
||||||
|
|
||||||
/// Fired after the user activity object has been updated.
|
/// Fired after the user activity object has been updated.
|
||||||
fn updated_user_activity(&self, _activity: UserActivity) {}
|
fn updated_user_activity(&self, _activity: UserActivity) {}
|
||||||
|
@ -56,7 +56,7 @@ pub trait AppDelegate {
|
||||||
|
|
||||||
/// Fired if there was a failure to register for remote notifications with APNS - e.g,
|
/// Fired if there was a failure to register for remote notifications with APNS - e.g,
|
||||||
/// connection issues or something.
|
/// connection issues or something.
|
||||||
fn failed_to_register_for_remote_notifications(&self, _error: AppKitError) {}
|
fn failed_to_register_for_remote_notifications(&self, _error: Error) {}
|
||||||
|
|
||||||
/// Fires after the user accepted a CloudKit sharing invitation associated with your
|
/// Fires after the user accepted a CloudKit sharing invitation associated with your
|
||||||
/// application.
|
/// application.
|
||||||
|
@ -124,7 +124,7 @@ pub trait AppDelegate {
|
||||||
/// Fired before the application presents an error message to the user. If you find the error
|
/// Fired before the application presents an error message to the user. If you find the error
|
||||||
/// to be... not what you want, you can take it, alter it, and return it anew. The default
|
/// to be... not what you want, you can take it, alter it, and return it anew. The default
|
||||||
/// implementation of this method simply returns the error as-is.
|
/// implementation of this method simply returns the error as-is.
|
||||||
fn will_present_error(&self, error: AppKitError) -> AppKitError { error }
|
fn will_present_error(&self, error: Error) -> Error { error }
|
||||||
|
|
||||||
/// Fired when the screen parameters for the application have changed (e.g, the user changed
|
/// Fired when the screen parameters for the application have changed (e.g, the user changed
|
||||||
/// something in their settings).
|
/// something in their settings).
|
||||||
|
|
|
@ -12,29 +12,50 @@
|
||||||
//!
|
//!
|
||||||
//! ## Example
|
//! ## Example
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
//use std::sync::Mutex;
|
||||||
use objc::runtime::Object;
|
//use std::collections::HashMap;
|
||||||
use objc_id::ShareId;
|
|
||||||
|
//use lazy_static::lazy_static;
|
||||||
|
//use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
//use objc::runtime::Object;
|
||||||
|
//use objc_id::ShareId;
|
||||||
|
|
||||||
mod traits;
|
mod traits;
|
||||||
pub use traits::Dispatcher;
|
pub use traits::Dispatcher;
|
||||||
|
|
||||||
/// Wraps a reference to an `NSNotificationCenter` instance. Currently this only supports the
|
/*lazy_static! {
|
||||||
/// default center; in the future it should aim to support custom variants.
|
pub static ref DefaultNotificationCenter: NotificationCenter = {
|
||||||
#[derive(Debug)]
|
NotificationCenter {
|
||||||
pub struct NotificationCenter(pub ShareId<Object>);
|
objc: unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSNotificationCenter), defaultCenter])
|
||||||
|
},
|
||||||
|
|
||||||
impl Default for NotificationCenter {
|
subscribers: Mutex::new(HashMap::new())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Wraps a reference to an `NSNotificationCenter` instance. Currently this only supports the
|
||||||
|
// default center; in the future it should aim to support custom variants.
|
||||||
|
//#[derive(Debug)]
|
||||||
|
//pub struct NotificationCenter {
|
||||||
|
// pub objc: ShareId<Object>,
|
||||||
|
//pub subscribers: Mutex<HashMap<String, Vec<Dispatcher>>>
|
||||||
|
//}
|
||||||
|
|
||||||
|
/*impl Default for NotificationCenter {
|
||||||
/// Returns a wrapper over `[NSNotificationCenter defaultCenter]`. From here you can handle
|
/// Returns a wrapper over `[NSNotificationCenter defaultCenter]`. From here you can handle
|
||||||
/// observing, removing, and posting notifications.
|
/// observing, removing, and posting notifications.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
NotificationCenter(unsafe {
|
NotificationCenter {
|
||||||
|
objc: unsafe {
|
||||||
ShareId::from_ptr(msg_send![class!(NSNotificationCenter), defaultCenter])
|
ShareId::from_ptr(msg_send![class!(NSNotificationCenter), defaultCenter])
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
impl NotificationCenter {
|
/*impl NotificationCenter {
|
||||||
pub fn observe<T: Dispatcher>(&self, name: &str, handler: &T) {
|
pub fn observe<T: Dispatcher>(&self, name: &str, handler: &T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,4 +67,4 @@ impl NotificationCenter {
|
||||||
pub fn post(&self, name: &str) {
|
pub fn post(&self, name: &str) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
|
@ -2,15 +2,13 @@
|
||||||
//! (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 std::error::Error;
|
|
||||||
|
|
||||||
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 url::Url;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, NSString, NSArray};
|
use crate::foundation::{id, nil, NSString, NSArray};
|
||||||
use crate::error::AppKitError;
|
use crate::error::Error;
|
||||||
|
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::{PasteboardName, PasteboardType};
|
pub use types::{PasteboardName, PasteboardType};
|
||||||
|
@ -66,7 +64,7 @@ impl Pasteboard {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Looks inside the pasteboard contents and extracts what FileURLs are there, if any.
|
/// 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>> {
|
pub fn get_file_urls(&self) -> Result<Vec<Url>, Box<dyn std::error::Error>> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let class: id = msg_send![class!(NSURL), class];
|
let class: id = msg_send![class!(NSURL), class];
|
||||||
let classes = NSArray::new(&[class]);
|
let classes = NSArray::new(&[class]);
|
||||||
|
@ -78,7 +76,7 @@ impl Pasteboard {
|
||||||
// This error is not necessarily "correct", but in the event of an error in
|
// 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
|
// Pasteboard server retrieval I'm not sure where to check... and this stuff is
|
||||||
// kinda ancient and has conflicting docs in places. ;P
|
// kinda ancient and has conflicting docs in places. ;P
|
||||||
return Err(Box::new(AppKitError {
|
return Err(Box::new(Error {
|
||||||
code: 666,
|
code: 666,
|
||||||
domain: "com.cacao-rs.pasteboard".to_string(),
|
domain: "com.cacao-rs.pasteboard".to_string(),
|
||||||
description: "Pasteboard server returned no data.".to_string()
|
description: "Pasteboard server returned no data.".to_string()
|
||||||
|
|
12
src/utils.rs
12
src/utils.rs
|
@ -10,7 +10,7 @@ use objc::{Encode, Encoding};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::id;
|
use crate::foundation::{id, BOOL};
|
||||||
|
|
||||||
/// A generic trait that's used throughout multiple different controls in this framework - acts as
|
/// 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.
|
/// a guard for whether something is a (View|etc)Controller. Only needs to return the backing node.
|
||||||
|
@ -81,3 +81,13 @@ pub fn activate_cocoa_multithreading() {
|
||||||
let _: () = msg_send![thread, start];
|
let _: () = msg_send![thread, start];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Utility method for turning an Objective-C `BOOL` into a Rust `bool`.
|
||||||
|
#[inline]
|
||||||
|
pub fn as_bool(value: BOOL) -> bool {
|
||||||
|
match value {
|
||||||
|
YES => true,
|
||||||
|
NO => false,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
//! This module does one specific thing: register a custom `NSView` class that's... brought to the
|
|
||||||
//! modern era.
|
|
||||||
//!
|
|
||||||
//! I kid, I kid.
|
|
||||||
//!
|
|
||||||
//! It just enforces that coordinates are judged from the top-left, which is what most people look
|
|
||||||
//! for in the modern era. It also implements a few helpers for things like setting a background
|
|
||||||
//! color, and enforcing layer backing by default.
|
|
||||||
|
|
||||||
use std::sync::Once;
|
|
||||||
|
|
||||||
use objc::declare::ClassDecl;
|
|
||||||
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::dragdrop::DragInfo;
|
|
||||||
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
|
|
||||||
use crate::utils::load;
|
|
||||||
|
|
||||||
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
|
||||||
extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
|
||||||
extern fn dragging_entered<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
|
|
||||||
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
view.dragging_entered(DragInfo {
|
|
||||||
info: unsafe { Id::from_ptr(info) }
|
|
||||||
}).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
|
||||||
extern fn prepare_for_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
|
||||||
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
|
|
||||||
match view.prepare_for_drag_operation(DragInfo {
|
|
||||||
info: unsafe { Id::from_ptr(info) }
|
|
||||||
}) {
|
|
||||||
true => YES,
|
|
||||||
false => NO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
|
||||||
extern fn perform_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
|
||||||
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
|
|
||||||
match view.perform_drag_operation(DragInfo {
|
|
||||||
info: unsafe { Id::from_ptr(info) }
|
|
||||||
}) {
|
|
||||||
true => YES,
|
|
||||||
false => NO
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
|
||||||
extern fn conclude_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) {
|
|
||||||
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
|
|
||||||
view.conclude_drag_operation(DragInfo {
|
|
||||||
info: unsafe { Id::from_ptr(info) }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when a drag/drop operation has entered this view.
|
|
||||||
extern fn dragging_exited<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) {
|
|
||||||
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
|
|
||||||
view.dragging_exited(DragInfo {
|
|
||||||
info: unsafe { Id::from_ptr(info) }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we
|
|
||||||
/// have separate classes here since we don't want to waste cycles on methods that will never be
|
|
||||||
/// used if there's no delegates.
|
|
||||||
pub(crate) fn register_view_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!(NSView);
|
|
||||||
let mut decl = ClassDecl::new("RSTView", superclass).unwrap();
|
|
||||||
|
|
||||||
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
|
||||||
|
|
||||||
VIEW_CLASS = decl.register();
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe { VIEW_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.
|
|
||||||
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
|
|
||||||
|
|
||||||
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, _, _));
|
|
||||||
|
|
||||||
VIEW_CLASS = decl.register();
|
|
||||||
});
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
VIEW_CLASS
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
//! Hoists a basic `NSViewController`.
|
|
||||||
|
|
||||||
use std::sync::Once;
|
|
||||||
|
|
||||||
use objc::declare::ClassDecl;
|
|
||||||
use objc::runtime::{Class, Object, Sel};
|
|
||||||
use objc::{class, sel, sel_impl};
|
|
||||||
|
|
||||||
use crate::view::{VIEW_DELEGATE_PTR, ViewDelegate};
|
|
||||||
use crate::utils::load;
|
|
||||||
|
|
||||||
/// Called when the view controller receives a `viewWillAppear` message.
|
|
||||||
extern fn will_appear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
|
||||||
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
controller.will_appear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when the view controller receives a `viewDidAppear` message.
|
|
||||||
extern fn did_appear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
|
||||||
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
controller.did_appear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when the view controller receives a `viewWillDisappear` message.
|
|
||||||
extern fn will_disappear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
|
||||||
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
controller.will_disappear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called when the view controller receives a `viewDidDisappear` message.
|
|
||||||
extern fn did_disappear<T: ViewDelegate>(this: &mut Object, _: Sel) {
|
|
||||||
let controller = load::<T>(this, VIEW_DELEGATE_PTR);
|
|
||||||
controller.did_disappear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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();
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
|
@ -5,9 +5,19 @@ use objc::{msg_send, sel, sel_impl};
|
||||||
use crate::foundation::id;
|
use crate::foundation::id;
|
||||||
use crate::layout::{Layout};
|
use crate::layout::{Layout};
|
||||||
use crate::view::{VIEW_DELEGATE_PTR, View, ViewDelegate};
|
use crate::view::{VIEW_DELEGATE_PTR, View, ViewDelegate};
|
||||||
|
use crate::utils::Controller;
|
||||||
|
|
||||||
mod class;
|
#[cfg(target_os = "macos")]
|
||||||
use class::register_view_controller_class;
|
mod macos;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use macos::register_view_controller_class;
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
mod ios;
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
use ios::register_view_controller_class;
|
||||||
|
|
||||||
//#[derive(Debug)]
|
//#[derive(Debug)]
|
||||||
pub struct ViewController<T> {
|
pub struct ViewController<T> {
|
||||||
|
@ -43,3 +53,9 @@ impl<T> ViewController<T> where T: ViewDelegate + 'static {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Controller for ViewController<T> {
|
||||||
|
fn get_backing_node(&self) -> ShareId<Object> {
|
||||||
|
self.objc.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -50,13 +50,23 @@ use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
use crate::pasteboard::PasteboardType;
|
use crate::pasteboard::PasteboardType;
|
||||||
|
|
||||||
mod class;
|
#[cfg(target_os = "macos")]
|
||||||
use class::{register_view_class, register_view_class_with_delegate};
|
mod macos;
|
||||||
|
|
||||||
pub mod controller;
|
#[cfg(target_os = "macos")]
|
||||||
|
use macos::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
pub mod traits;
|
#[cfg(target_os = "ios")]
|
||||||
use traits::ViewDelegate;
|
mod ios;
|
||||||
|
|
||||||
|
#[cfg(target_os = "ios")]
|
||||||
|
use ios::{register_view_class, register_view_class_with_delegate};
|
||||||
|
|
||||||
|
mod controller;
|
||||||
|
pub use controller::ViewController;
|
||||||
|
|
||||||
|
mod traits;
|
||||||
|
pub use traits::ViewDelegate;
|
||||||
|
|
||||||
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
|
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
|
||||||
|
|
||||||
|
@ -65,7 +75,10 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
let view: id = msg_send![registration_fn(), new];
|
let view: id = msg_send![registration_fn(), new];
|
||||||
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
let _: () = msg_send![view, setWantsLayer:YES];
|
let _: () = msg_send![view, setWantsLayer:YES];
|
||||||
|
|
||||||
view
|
view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,16 @@ pub trait ViewDelegate {
|
||||||
fn did_load(&self, _view: View) {}
|
fn did_load(&self, _view: View) {}
|
||||||
|
|
||||||
/// Called when this is about to be added to the view heirarchy.
|
/// Called when this is about to be added to the view heirarchy.
|
||||||
fn will_appear(&self) {}
|
fn will_appear(&self, animated: bool) {}
|
||||||
|
|
||||||
/// Called after this has been added to the view heirarchy.
|
/// Called after this has been added to the view heirarchy.
|
||||||
fn did_appear(&self) {}
|
fn did_appear(&self, animated: bool) {}
|
||||||
|
|
||||||
/// Called when this is about to be removed from the view heirarchy.
|
/// Called when this is about to be removed from the view heirarchy.
|
||||||
fn will_disappear(&self) {}
|
fn will_disappear(&self, animated: bool) {}
|
||||||
|
|
||||||
/// Called when this has been removed from the view heirarchy.
|
/// Called when this has been removed from the view heirarchy.
|
||||||
fn did_disappear(&self) {}
|
fn did_disappear(&self, animated: bool) {}
|
||||||
|
|
||||||
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
|
/// 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 }
|
||||||
|
|
Loading…
Reference in a new issue