Experimental iOS support - delegate pattern working in simulator, need to figure out scenes next.
This commit is contained in:
parent
ab53150abc
commit
47ddf7f5a3
|
@ -22,8 +22,9 @@ url = "2.1.1"
|
|||
|
||||
[features]
|
||||
default = ["macos"]
|
||||
macos = []
|
||||
cloudkit = []
|
||||
ios = []
|
||||
macos = []
|
||||
user-notifications = ["uuid"]
|
||||
webview = []
|
||||
webview-downloading = []
|
||||
|
|
34
build.rs
34
build.rs
|
@ -4,21 +4,27 @@
|
|||
//! (it checks to see if it's macOS before emitting anything, but still)
|
||||
|
||||
fn main() {
|
||||
if std::env::var("TARGET").unwrap().contains("-apple") {
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
println!("cargo:rustc-link-lib=framework=Cocoa");
|
||||
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
||||
println!("cargo:rustc-link-lib=framework=QuartzCore");
|
||||
let target = std::env::var("TARGET").unwrap();
|
||||
|
||||
println!("cargo:rustc-link-lib=framework=Security");
|
||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
||||
|
||||
#[cfg(feature = "webview")]
|
||||
println!("cargo:rustc-link-lib=framework=WebKit");
|
||||
|
||||
#[cfg(feature = "cloudkit")]
|
||||
println!("cargo:rustc-link-lib=framework=CloudKit");
|
||||
|
||||
#[cfg(feature = "user-notifications")]
|
||||
println!("cargo:rustc-link-lib=framework=UserNotifications");
|
||||
if std::env::var("TARGET").unwrap().contains("-ios") {
|
||||
println!("cargo:rustc-link-lib=framework=UIKit");
|
||||
} else {
|
||||
println!("cargo:rustc-link-lib=framework=AppKit");
|
||||
}
|
||||
|
||||
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
||||
println!("cargo:rustc-link-lib=framework=QuartzCore");
|
||||
|
||||
println!("cargo:rustc-link-lib=framework=Security");
|
||||
|
||||
#[cfg(feature = "webview")]
|
||||
println!("cargo:rustc-link-lib=framework=WebKit");
|
||||
|
||||
#[cfg(feature = "cloudkit")]
|
||||
println!("cargo:rustc-link-lib=framework=CloudKit");
|
||||
|
||||
#[cfg(feature = "user-notifications")]
|
||||
println!("cargo:rustc-link-lib=framework=UserNotifications");
|
||||
}
|
||||
|
|
25
src/ios/app/class.rs
Normal file
25
src/ios/app/class.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
//! 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::sync::Once;
|
||||
|
||||
use objc::class;
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class};
|
||||
|
||||
/// Used for injecting a custom UIApplication. Currently does nothing.
|
||||
pub(crate) fn register_app_class() -> *const Class {
|
||||
static mut APP_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = class!(UIApplication);
|
||||
let decl = ClassDecl::new("RSTApplication", superclass).unwrap();
|
||||
APP_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe {
|
||||
APP_CLASS
|
||||
}
|
||||
}
|
63
src/ios/app/delegate.rs
Normal file
63
src/ios/app/delegate.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
//! 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::AppKitError;
|
||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
||||
use crate::ios::app::{APP_PTR, AppDelegate, APP_DELEGATE};
|
||||
use crate::user_activity::UserActivity;
|
||||
|
||||
#[cfg(feature = "cloudkit")]
|
||||
use crate::cloudkit::share::CKShareMetaData;
|
||||
|
||||
/// A handy method for grabbing our `AppDelegate` from the pointer. This is different from our
|
||||
/// standard `utils` version as this doesn't require `RefCell` backing.
|
||||
fn app<T>(this: &Object) -> &T {
|
||||
unsafe {
|
||||
//let app_ptr: usize = *this.get_ivar(APP_DELEGATE);
|
||||
let app = APP_DELEGATE as *const T;
|
||||
&*app
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
||||
extern fn did_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id, _: id) -> BOOL {
|
||||
println!("HMMMM");
|
||||
app::<T>(this).did_finish_launching();
|
||||
YES
|
||||
}
|
||||
|
||||
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
|
||||
/// pointers we need to have.
|
||||
pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
|
||||
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = class!(NSObject);
|
||||
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();
|
||||
|
||||
decl.add_ivar::<usize>(APP_PTR);
|
||||
|
||||
// Launching Applications
|
||||
decl.add_method(sel!(application:didFinishLaunchingWithOptions:), did_finish_launching::<T> as extern fn(&Object, _, _, id) -> BOOL);
|
||||
|
||||
DELEGATE_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe {
|
||||
DELEGATE_CLASS
|
||||
}
|
||||
}
|
4
src/ios/app/enums.rs
Normal file
4
src/ios/app/enums.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//! Various types used at the AppController level.
|
||||
|
||||
use crate::foundation::NSUInteger;
|
||||
|
199
src/ios/app/mod.rs
Normal file
199
src/ios/app/mod.rs
Normal file
|
@ -0,0 +1,199 @@
|
|||
//! Wraps the application lifecycle across platforms.
|
||||
//!
|
||||
//! This is where the bulk of your application logic starts out from. macOS and iOS are driven
|
||||
//! heavily by lifecycle events - in this case, your boilerplate would look something like this:
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use cacao::app::{App, AppDelegate};
|
||||
//! use cacao::window::Window;
|
||||
//!
|
||||
//! #[derive(Default)]
|
||||
//! struct BasicApp;
|
||||
//!
|
||||
//! impl AppDelegate for BasicApp {
|
||||
//! fn did_finish_launching(&self) {
|
||||
//! // Your program in here
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn main() {
|
||||
//! App::new("com.my.app", BasicApp::default()).run();
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Why do I need to do this?
|
||||
//! A good question. Cocoa does many things for you (e.g, setting up and managing a runloop,
|
||||
//! handling the view/window heirarchy, and so on). This requires certain things happen before your
|
||||
//! code can safely run, which `App` in this framework does for you.
|
||||
//!
|
||||
//! - It ensures that the `sharedApplication` is properly initialized with your delegate.
|
||||
//! - It ensures that Cocoa is put into multi-threaded mode, so standard POSIX threads work as they
|
||||
//! should.
|
||||
//!
|
||||
//! ### Platform specificity
|
||||
//! Certain lifecycle events are specific to certain platforms. Where this is the case, the
|
||||
//! documentation makes every effort to note.
|
||||
|
||||
use libc::{c_char, c_int};
|
||||
use std::ffi::CString;
|
||||
|
||||
use objc_id::Id;
|
||||
use objc::runtime::Object;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::{id, nil, YES, NO, NSString, NSUInteger, AutoReleasePool};
|
||||
use crate::notification_center::Dispatcher;
|
||||
|
||||
mod class;
|
||||
use class::register_app_class;
|
||||
|
||||
mod delegate;
|
||||
use delegate::{register_app_delegate_class};
|
||||
|
||||
mod enums;
|
||||
pub use enums::*;
|
||||
|
||||
mod traits;
|
||||
pub use traits::AppDelegate;
|
||||
|
||||
pub(crate) static APP_PTR: &str = "rstAppPtr";
|
||||
pub(crate) static mut APP_DELEGATE: usize = 0;
|
||||
|
||||
//#[link(name="UIApplicationMain")]
|
||||
extern "C" {
|
||||
fn UIApplicationMain(
|
||||
argc: c_int,
|
||||
argv: *const *const c_char,
|
||||
principal_class_name: id,
|
||||
delegate_class_name: id
|
||||
);
|
||||
}
|
||||
|
||||
/// A handler to make some boilerplate less annoying.
|
||||
#[inline]
|
||||
fn shared_application<F: Fn(id)>(handler: F) {
|
||||
let app: id = unsafe { msg_send![register_app_class(), sharedApplication] };
|
||||
handler(app);
|
||||
}
|
||||
|
||||
/// A helper method for ensuring that Cocoa is running in multi-threaded mode.
|
||||
///
|
||||
/// Why do we need this? According to Apple, if you're going to make use of standard POSIX threads,
|
||||
/// you need to, before creating and using a POSIX thread, first create and immediately detach a
|
||||
/// `NSThread`. This ensures that Cocoa utilizes proper locking in certain places where it might
|
||||
/// not be doing so for performance reasons.
|
||||
///
|
||||
/// In general, you should aim to just start all of your work inside of your `AppDelegate` methods.
|
||||
/// There are some cases where you might want to do things before that, though - and if you spawn a
|
||||
/// thread there, just call this first... otherwise you may have some unexpected issues later on.
|
||||
///
|
||||
/// _(This is called inside the `App::new()` construct for you already, so as long as you're doing
|
||||
/// nothing before your `AppDelegate`, you can pay this no mind)._
|
||||
pub fn activate_cocoa_multithreading() {
|
||||
unsafe {
|
||||
let thread: id = msg_send![class!(NSThread), new];
|
||||
let _: () = msg_send![thread, start];
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for `NSApplication` on macOS, and `UIApplication` on iOS.
|
||||
///
|
||||
/// It holds (retains) a pointer to the Objective-C runtime shared application object, as well as
|
||||
/// handles setting up a few necessary pieces:
|
||||
///
|
||||
/// - 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
|
||||
/// threads work as intended.
|
||||
///
|
||||
/// This also enables support for dispatching a message, `M`. Your `AppDelegate` can optionally
|
||||
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
|
||||
/// application.
|
||||
pub struct App<T = (), M = ()> {
|
||||
//pub inner: Id<Object>,
|
||||
//pub objc_delegate: Id<Object>,
|
||||
pub delegate: Box<T>,
|
||||
pub pool: AutoReleasePool,
|
||||
_t: std::marker::PhantomData<M>
|
||||
}
|
||||
|
||||
impl<T> App<T> {
|
||||
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
|
||||
/// If you're wondering where to go from here... you need an `AppDelegate` that implements
|
||||
/// `did_finish_launching`. :)
|
||||
pub fn run(&self) {
|
||||
unsafe {
|
||||
// create a vector of zero terminated strings
|
||||
let args = std::env::args().map(|arg| CString::new(arg).unwrap() ).collect::<Vec<CString>>();
|
||||
|
||||
// convert the strings to raw pointers
|
||||
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
|
||||
|
||||
let s = NSString::new("RSTApplication");
|
||||
let s2 = NSString::new("RSTAppDelegate");
|
||||
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into_inner(), s2.into_inner());
|
||||
self.pool.drain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> App<T> where T: AppDelegate + 'static {
|
||||
/// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation
|
||||
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
||||
/// Objective-C side of things.
|
||||
pub fn new(_bundle_id: &str, delegate: T) -> Self {
|
||||
// set_bundle_id(bundle_id);
|
||||
|
||||
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;
|
||||
}
|
||||
//(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
|
||||
|
||||
|
||||
/*let inner = unsafe {
|
||||
let app: id = msg_send![register_app_class(), sharedApplication];
|
||||
let _: () = msg_send![app, setActivationPolicy:0];
|
||||
println!("1");
|
||||
Id::from_ptr(app)
|
||||
};
|
||||
|
||||
let objc_delegate = unsafe {
|
||||
let delegate_class = register_app_delegate_class::<T>();
|
||||
let delegate: id = msg_send![delegate_class, new];
|
||||
let _: () = msg_send![&*inner, setDelegate:delegate];
|
||||
println!("2");
|
||||
Id::from_ptr(delegate)
|
||||
};*/
|
||||
|
||||
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) {
|
||||
/*
|
||||
let queue = dispatch::Queue::main();
|
||||
|
||||
queue.exec_async(move || unsafe {
|
||||
let app: id = msg_send![register_app_class(), sharedApplication];
|
||||
let app_delegate: id = msg_send![app, delegate];
|
||||
let delegate_ptr: usize = *(*app_delegate).get_ivar(APP_PTR);
|
||||
let delegate = delegate_ptr as *const T;
|
||||
(&*delegate).on_message(message);
|
||||
});*/
|
||||
}
|
||||
}
|
21
src/ios/app/traits.rs
Normal file
21
src/ios/app/traits.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
//! 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.
|
||||
|
||||
use url::Url;
|
||||
|
||||
use crate::error::AppKitError;
|
||||
use crate::user_activity::UserActivity;
|
||||
|
||||
#[cfg(feature = "cloudkit")]
|
||||
use crate::cloudkit::share::CKShareMetaData;
|
||||
|
||||
/// `AppDelegate` is more or less `NSApplicationDelegate` from the Objective-C/Swift side, just named
|
||||
/// differently to fit in with the general naming scheme found within this framework. You can
|
||||
/// implement methods from this trait in order to respond to lifecycle events that the system will
|
||||
/// fire off.
|
||||
pub trait AppDelegate {
|
||||
/// Fired when the application has finished launching. Unlike most other "load" lifecycle
|
||||
/// 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.
|
||||
fn did_finish_launching(&self) {}
|
||||
}
|
4
src/ios/mod.rs
Normal file
4
src/ios/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//! iOS.
|
||||
|
||||
mod app;
|
||||
pub use app::*;
|
|
@ -75,6 +75,9 @@ pub use url;
|
|||
#[cfg(feature = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(feature = "ios")]
|
||||
pub mod ios;
|
||||
|
||||
pub mod button;
|
||||
|
||||
#[cfg(feature = "cloudkit")]
|
||||
|
|
Loading…
Reference in a new issue