Very rough experimental support for iOS13+ Scenes. Not ready for use or comment yet.

This commit is contained in:
Ryan McGrath 2020-04-04 19:50:58 -07:00
parent 0c604c2e84
commit 4ff69c008a
No known key found for this signature in database
GPG key ID: 811674B62B666830
28 changed files with 550 additions and 288 deletions

View file

@ -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"

View file

@ -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 {}

View file

@ -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 {

View file

@ -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
}
}
}

View file

@ -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();
}); });

View file

@ -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;

View file

@ -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) {
}
}

View file

@ -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;
} }

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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];
}
}
}

View file

@ -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.

View file

@ -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.

View file

@ -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).

View file

@ -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 {
ShareId::from_ptr(msg_send![class!(NSNotificationCenter), defaultCenter]) objc: unsafe {
}) 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) {
} }
} }*/

View file

@ -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()

View file

@ -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!()
}
}

View file

@ -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
}
}

View file

@ -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 }
}

View file

@ -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()
}
}

View file

@ -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
} }
} }

View file

@ -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 }