Moving to a model where ther are actual examples, since the handler logic is finally ironed out well.

This commit is contained in:
Ryan McGrath 2020-03-19 20:07:44 -07:00
parent bd82b8f27b
commit f45c86743b
No known key found for this signature in database
GPG key ID: 811674B62B666830
100 changed files with 1429 additions and 1133 deletions

2
.gitignore vendored
View file

@ -1,4 +1,2 @@
target
Cargo.lock
placeholder
placeholder2

View file

@ -5,6 +5,9 @@ authors = ["Ryan McGrath <ryan@rymc.io>"]
edition = "2018"
build = "build.rs"
[lib]
path = "lib.rs"
[dependencies]
block = "0.1.6"
dispatch = "0.2.0"
@ -18,4 +21,4 @@ url = "2.1.1"
cloudkit = []
user-notifications = ["uuid"]
webview = []
enable-webview-downloading = []
webview-downloading = []

View file

@ -15,7 +15,7 @@ use objc::runtime::{Class, Object, Sel};
use url::Url;
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
use crate::app::traits::AppController;
use crate::app::traits::AppDelegate;
use crate::constants::APP_PTR;
use crate::error::AppKitError;
use crate::printing::PrintSettings;
@ -24,9 +24,9 @@ use crate::user_activity::UserActivity;
#[cfg(feature = "cloudkit")]
use crate::cloudkit::share::CKShareMetaData;
/// A handy method for grabbing our `AppController` from the pointer. This is different from our
/// 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: AppController>(this: &Object) -> &T {
fn app<T: AppDelegate>(this: &Object) -> &T {
unsafe {
let app_ptr: usize = *this.get_ivar(APP_PTR);
let app = app_ptr as *const T;
@ -35,78 +35,78 @@ fn app<T: AppController>(this: &Object) -> &T {
}
/// Fires when the Application Delegate receives a `applicationWillFinishLaunching` notification.
extern fn will_finish_launching<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_finish_launching();
}
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
extern fn did_finish_launching<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_finish_launching();
}
/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification.
extern fn will_become_active<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_become_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_become_active();
}
/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification.
extern fn did_become_active<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_become_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_become_active();
}
/// Fires when the Application Delegate receives a `applicationWillResignActive` notification.
extern fn will_resign_active<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_resign_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_resign_active();
}
/// Fires when the Application Delegate receives a `applicationDidResignActive` notification.
extern fn did_resign_active<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_resign_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_resign_active();
}
/// Fires when the Application Delegate receives a 'applicationShouldTerminate:` notification.
extern fn should_terminate<T: AppController>(this: &Object, _: Sel, _: id) -> NSUInteger {
extern fn should_terminate<T: AppDelegate>(this: &Object, _: Sel, _: id) -> NSUInteger {
app::<T>(this).should_terminate().into()
}
/// Fires when the Application Delegate receives a `applicationWillTerminate:` notification.
extern fn will_terminate<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_terminate<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_terminate();
}
/// Fires when the Application Delegate receives a `applicationWillHide:` notification.
extern fn will_hide<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_hide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_hide();
}
/// Fires when the Application Delegate receives a `applicationDidHide:` notification.
extern fn did_hide<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_hide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_hide();
}
/// Fires when the Application Delegate receives a `applicationWillUnhide:` notification.
extern fn will_unhide<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_unhide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_unhide();
}
/// Fires when the Application Delegate receives a `applicationDidUnhide:` notification.
extern fn did_unhide<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_unhide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_unhide();
}
/// Fires when the Application Delegate receives a `applicationWillUpdate:` notification.
extern fn will_update<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn will_update<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_update();
}
/// Fires when the Application Delegate receives a `applicationDidUpdate:` notification.
extern fn did_update<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_update<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_update();
}
/// Fires when the Application Delegate receives a
/// `applicationShouldHandleReopen:hasVisibleWindows:` notification.
extern fn should_handle_reopen<T: AppController>(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL {
extern fn should_handle_reopen<T: AppDelegate>(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL {
match app::<T>(this).should_handle_reopen(match has_visible_windows {
YES => true,
NO => false,
@ -118,7 +118,7 @@ extern fn should_handle_reopen<T: AppController>(this: &Object, _: Sel, _: id, h
}
/// Fires when the application delegate receives a `applicationDockMenu:` request.
extern fn dock_menu<T: AppController>(this: &Object, _: Sel, _: id) -> id {
extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
match app::<T>(this).dock_menu() {
Some(mut menu) => &mut *menu.inner,
None => nil
@ -126,19 +126,19 @@ extern fn dock_menu<T: AppController>(this: &Object, _: Sel, _: id) -> id {
}
/// Fires when the application delegate receives a `application:willPresentError:` notification.
extern fn will_present_error<T: AppController>(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);
app::<T>(this).will_present_error(error).into_nserror()
}
/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification.
extern fn did_change_screen_parameters<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_change_screen_parameters<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_change_screen_parameters();
}
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
/// notification.
extern fn will_continue_user_activity_with_type<T: AppController>(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
extern fn will_continue_user_activity_with_type<T: AppDelegate>(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
let activity = NSString::wrap(activity_type);
match app::<T>(this).will_continue_user_activity(activity.to_str()) {
@ -148,7 +148,7 @@ extern fn will_continue_user_activity_with_type<T: AppController>(this: &Object,
}
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
extern fn continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
extern fn continue_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
// @TODO: This needs to support restorable objects, but it involves a larger question about how
// much `NSObject` wrapping we want to do here. For now, pass the handler for whenever it's
// useful.
@ -165,7 +165,7 @@ extern fn continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id,
/// Fires when the application receives a
/// `application:didFailToContinueUserActivityWithType:error:` message.
extern fn failed_to_continue_user_activity<T: AppController>(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(
NSString::wrap(activity_type).to_str(),
AppKitError::new(error)
@ -173,36 +173,36 @@ extern fn failed_to_continue_user_activity<T: AppController>(this: &Object, _: S
}
/// Fires when the application receives a `application:didUpdateUserActivity:` message.
extern fn did_update_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id) {
extern fn did_update_user_activity<T: AppDelegate>(this: &Object, _: Sel, _: id, activity: id) {
let activity = UserActivity::with_inner(activity);
app::<T>(this).updated_user_activity(activity);
}
/// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message.
extern fn registered_for_remote_notifications<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
extern fn registered_for_remote_notifications<T: AppDelegate>(_this: &Object, _: Sel, _: id, _: id) {
}
/// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message.
extern fn failed_to_register_for_remote_notifications<T: AppController>(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));
}
/// Fires when the application receives a `application:didReceiveRemoteNotification:` message.
extern fn did_receive_remote_notification<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
extern fn did_receive_remote_notification<T: AppDelegate>(_this: &Object, _: Sel, _: id, _: id) {
}
/// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:`
/// message.
#[cfg(feature = "cloudkit")]
extern fn accepted_cloudkit_share<T: AppController>(_this: &Object, _: Sel, _: id, metadata: id) {
extern fn accepted_cloudkit_share<T: AppDelegate>(_this: &Object, _: Sel, _: id, metadata: id) {
let share = CKShareMetaData::with_inner(metadata);
app::<T>(this).user_accepted_cloudkit_share(share);
}
/// Fires when the application receives an `application:openURLs` message.
extern fn open_urls<T: AppController>(this: &Object, _: Sel, _: id, file_urls: id) {
extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
let urls = NSArray::wrap(file_urls).map(|url| {
let uri = NSString::wrap(unsafe {
msg_send![url, absoluteString]
@ -215,7 +215,7 @@ extern fn open_urls<T: AppController>(this: &Object, _: Sel, _: id, file_urls: i
}
/// Fires when the application receives an `application:openFileWithoutUI:` message.
extern fn open_file_without_ui<T: AppController>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
extern fn open_file_without_ui<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
let filename = NSString::wrap(file);
match app::<T>(this).open_file_without_ui(filename.to_str()) {
@ -225,7 +225,7 @@ extern fn open_file_without_ui<T: AppController>(this: &Object, _: Sel, _: id, f
}
/// Fired when the application receives an `applicationShouldOpenUntitledFile:` message.
extern fn should_open_untitled_file<T: AppController>(this: &Object, _: Sel, _: id) -> BOOL {
extern fn should_open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
match app::<T>(this).should_open_untitled_file() {
true => YES,
false => NO
@ -233,7 +233,7 @@ extern fn should_open_untitled_file<T: AppController>(this: &Object, _: Sel, _:
}
/// Fired when the application receives an `applicationOpenUntitledFile:` message.
extern fn open_untitled_file<T: AppController>(this: &Object, _: Sel, _: id) -> BOOL {
extern fn open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
match app::<T>(this).open_untitled_file() {
true => YES,
false => NO
@ -241,7 +241,7 @@ extern fn open_untitled_file<T: AppController>(this: &Object, _: Sel, _: id) ->
}
/// Fired when the application receives an `application:openTempFile:` message.
extern fn open_temp_file<T: AppController>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
extern fn open_temp_file<T: AppDelegate>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
let filename = NSString::wrap(filename);
match app::<T>(this).open_temp_file(filename.to_str()) {
@ -251,7 +251,7 @@ extern fn open_temp_file<T: AppController>(this: &Object, _: Sel, _: id, filenam
}
/// Fired when the application receives an `application:printFile:` message.
extern fn print_file<T: AppController>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
extern fn print_file<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
let filename = NSString::wrap(file);
match app::<T>(this).print_file(filename.to_str()) {
@ -262,7 +262,7 @@ extern fn print_file<T: AppController>(this: &Object, _: Sel, _: id, file: id) -
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
/// message.
extern fn print_files<T: AppController>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
extern fn print_files<T: AppDelegate>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
let files = NSArray::wrap(files).map(|file| {
NSString::wrap(file).to_str().to_string()
});
@ -277,14 +277,14 @@ extern fn print_files<T: AppController>(this: &Object, _: Sel, _: id, files: id,
}
/// Called when the application's occlusion state has changed.
extern fn did_change_occlusion_state<T: AppController>(this: &Object, _: Sel, _: id) {
extern fn did_change_occlusion_state<T: AppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).occlusion_state_changed();
}
/// Called when the application receives an `application:delegateHandlesKey:` message.
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
/// matter.
extern fn delegate_handles_key<T: AppController>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
extern fn delegate_handles_key<T: AppDelegate>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
let key = NSString::wrap(key);
match app::<T>(this).delegate_handles_key(key.to_str()) {
@ -295,13 +295,13 @@ extern fn delegate_handles_key<T: AppController>(this: &Object, _: Sel, _: id, k
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
/// pointers we need to have.
pub(crate) fn register_app_controller_class<T: AppController>() -> *const Class {
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("RSTAppController", superclass).unwrap();
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();
decl.add_ivar::<usize>(APP_PTR);

View file

@ -12,13 +12,13 @@ use crate::menu::Menu;
mod class;
use class::register_app_class;
mod controller;
use controller::register_app_controller_class;
mod delegate;
use delegate::register_app_delegate_class;
pub mod enums;
pub mod traits;
pub use traits::{AppController, Dispatcher};
pub use traits::{AppDelegate, Dispatcher};
/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,
/// which is where our application instance lives. It also injects an `NSObject` subclass,
@ -54,7 +54,7 @@ impl App {
}
}
impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppController + Dispatcher<Message = M> {
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.
@ -72,7 +72,7 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppController + Dispatch
let app_delegate = Box::new(delegate);
let objc_delegate = unsafe {
let delegate_class = register_app_controller_class::<T>();
let delegate_class = register_app_delegate_class::<T>();
let delegate: id = msg_send![delegate_class, new];
let delegate_ptr: *const T = &*app_delegate;
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
@ -89,6 +89,21 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppController + Dispatch
}
}
/// 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 {
let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
let _: () = msg_send![current_app, activateWithOptions:1<<1];
let shared_app: id = msg_send![class!(RSTApplication), sharedApplication];
let _: () = msg_send![shared_app, run];
self.pool.drain();
}
}
}
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) {
@ -102,17 +117,4 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppController + Dispatch
(&*delegate).on_message(message);
});
}
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
/// If you're wondering where to go from here... you need an `AppController` that implements
/// `did_finish_launching`. :)
pub fn run(&self) {
unsafe {
let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
let _: () = msg_send![current_app, activateWithOptions:1<<1];
let shared_app: id = msg_send![class!(RSTApplication), sharedApplication];
let _: () = msg_send![shared_app, run];
self.pool.drain();
}
}
}

View file

@ -21,11 +21,11 @@ pub trait Dispatcher {
fn on_message(&self, _message: Self::Message) {}
}
/// `AppController` is more or less `AppDelegate` from the Objective-C/Swift side, just named
/// `AppDelegate` is more or less `NSAppDelegate` 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 AppController {
pub trait AppDelegate {
/// Called right before the application will finish launching. You really, probably, want to do
/// your setup in `did_finish_launching` unless you're sure of what you're doing.
fn will_finish_launching(&self) {}

View file

@ -6,6 +6,7 @@
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=Security");

View file

@ -4,7 +4,7 @@
pub(crate) static APP_PTR: &str = "rstAppPtr";
pub(crate) static BACKGROUND_COLOR: &str = "rstBackgroundColor";
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr";
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
#[cfg(feature = "webview")]
pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig";
@ -14,5 +14,3 @@ pub(crate) static WEBVIEW_VAR: &str = "rstWebView";
#[cfg(feature = "webview")]
pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";
pub(crate) static WINDOW_CONTROLLER_PTR: &str = "rstWindowController";

View file

@ -3,6 +3,7 @@
use crate::foundation::{CGRect, CGPoint, CGSize};
/// A struct that represents a box - top, left, width and height.
#[derive(Copy, Clone, Debug)]
pub struct Rect {
/// Distance from the top, in points.
pub top: f64,

View file

@ -9,7 +9,7 @@ pub trait Layout {
/// Returns a reference to the backing Objective-C layer. This is optional, as we try to keep
/// the general lazy-loading approach Cocoa has. This may change in the future, and in general
/// this shouldn't affect your code too much (if at all).
fn get_backing_node(&self) -> Option<ShareId<Object>>;
fn get_backing_node(&self) -> ShareId<Object>;
/// This trait should implement adding a view to the subview tree for a given view.
fn add_subview<V: Layout>(&self, _view: &V);

View file

@ -44,7 +44,7 @@ pub mod printing;
pub mod toolbar;
pub mod user_activity;
pub mod utils;
pub mod view;
//pub mod view;
#[cfg(feature = "webview")]
pub mod webview;
@ -55,7 +55,7 @@ pub mod window;
pub use url;
pub mod prelude {
pub use crate::app::{App, AppController, Dispatcher};
pub use crate::app::{App, AppDelegate, Dispatcher};
pub use crate::layout::LayoutConstraint;
@ -69,7 +69,7 @@ pub mod prelude {
pub use crate::networking::URLRequest;
pub use crate::window::{
Window, WindowController, WindowHandle
Window, /*WindowController,*/ WindowDelegate
};
#[cfg(feature = "webview")]
@ -77,5 +77,5 @@ pub mod prelude {
WebView, WebViewConfig, WebViewController
};
pub use crate::view::{View, ViewHandle, ViewController};
//pub use crate::view::{View, ViewController, ViewDelegate};
}

View file

@ -1,45 +0,0 @@
//! Hoists a basic `NSViewController`. We use `NSViewController` rather than plain `NSView` as
//! we're interested in the lifecycle methods and events.
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, NO, CGRect};
use crate::constants::VIEW_CONTROLLER_PTR;
use crate::geometry::Rect;
use crate::view::ViewController;
use crate::view::class::register_view_class;
/// Loads and configures ye old NSView for this controller.
extern fn load_view<T: ViewController>(this: &mut Object, _: Sel) {
unsafe {
let zero: CGRect = Rect::zero().into();
let view: id = msg_send![register_view_class::<T>(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let _: () = msg_send![view, setFrame:zero];
let _: () = msg_send![this, setView:view];
}
}
/// Registers an `NSViewController`.
pub fn register_controller_class<T: ViewController + '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_CONTROLLER_PTR);
// NSViewController
decl.add_method(sel!(loadView), load_view::<T> as extern fn(&mut Object, _));
VIEW_CLASS = decl.register();
});
unsafe { VIEW_CLASS }
}

View file

@ -1,44 +0,0 @@
//! A wrapper for `WKWebViewConfiguration`. It aims to (mostly) cover
//! the important pieces of configuring and updating a WebView configuration.
use cocoa::base::{id, nil, YES};
use cocoa::foundation::NSString;
use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
/// Whether a script should be injected at the start or end of the document load.
pub enum InjectAt {
Start = 0,
End = 1
}
/// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime
/// where everything lives.
pub struct WebViewConfig {
pub inner: Id<Object>
}
impl Default for WebViewConfig {
fn default() -> Self {
let inner = unsafe {
let cls = class!(WKWebViewConfiguration);
let inner: id = msg_send![cls, new];
// For debug builds, we want to enable this as it allows the inspector to be used.
if cfg!(debug_assertions) {
let key = NSString::alloc(nil).init_str("developerExtrasEnabled");
let yes: id = msg_send![class!(NSNumber), numberWithBool:YES];
let preferences: id = msg_send![inner, preferences];
let _: () = msg_send![preferences, setValue:yes forKey:key];
}
Id::from_ptr(inner)
};
WebViewConfig {
inner: inner
}
}
}

View file

@ -1,15 +0,0 @@
//! A wrapper for WKWebview and associated configurations and properties.
pub mod action;
pub(crate) mod controller;
//pub(crate) mod process_pool;
pub mod traits;
pub use traits::{WebViewController};
pub mod config;
pub use config::{WebViewConfig, InjectAt};
pub mod webview;
pub use webview::WebView;

View file

@ -1,126 +0,0 @@
//! Implements a WebView, which wraps a number of different classes/delegates/controllers into one
//! useful interface. This encompasses...
//!
//! - `WKWebView`
//! - `WKUIDelegate`
//! - `WKScriptMessageHandler`
//! - `NSViewController`
//!
//! ...yeah.
use std::rc::Rc;
use std::cell::RefCell;
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSString};
use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
use crate::view::ViewController;
use crate::webview::config::{WebViewConfig, InjectAt};
use crate::webview::controller::register_controller_class;
use crate::webview::traits::WebViewController;
#[derive(Default)]
pub struct WebViewInner {
pub config: WebViewConfig,
pub controller: Option<ShareId<Object>>
}
impl WebViewInner {
pub fn configure<T: ViewController + WebViewController + 'static>(&mut self, controller: &T) {
self.controller = Some(unsafe {
let view_controller: id = msg_send![register_controller_class::<T>(), new];
(&mut *view_controller).set_ivar(WEBVIEW_CONFIG_VAR, &*self.config.inner);
(&mut *view_controller).set_ivar(WEBVIEW_CONTROLLER_PTR, controller as *const T as usize);
ShareId::from_ptr(view_controller)
});
}
// Builder pattern?
// let webview: id = msg_send![view_controller, view];
// let _: () = msg_send![webview, setUIDelegate:view_controller];
pub fn add_user_script(&self, script: &str, inject_at: InjectAt, main_frame_only: bool) {
unsafe {
let source = NSString::alloc(nil).init_str(script);
let cls = class!(WKUserScript);
let alloc: id = msg_send![cls, alloc];
let user_script: id = msg_send![alloc, initWithSource:source injectionTime:inject_at forMainFrameOnly:match main_frame_only {
true => YES,
false => NO
}];
let content_controller: id = msg_send![&*self.config.inner, userContentController];
let _: () = msg_send![content_controller, addUserScript:user_script];
}
}
pub fn add_handler(&self, name: &str) {
if let Some(controller) = &self.controller {
unsafe {
let name = NSString::alloc(nil).init_str(name);
let content_controller: id = msg_send![&*self.config.inner, userContentController];
let _: () = msg_send![content_controller, addScriptMessageHandler:controller.clone() name:name];
}
}
}
pub fn load_url(&self, url: &str) {
if let Some(controller) = &self.controller {
unsafe {
// This is weird, I know, but it has to be done due to a lifecycle "quirk" in AppKit.
// In short: `loadView` isn't called unless the view is actually accessed, and you
// could theoretically call this without having had it done. We use the `loadView`
// method because we *want* the lazy loading aspect, but for this call to work we
// need things to be done.
//
// We can't create the `WKWebView` before `loadView` as it copies
// `WKWebViewConfiguration` on initialization, and we defer that for API reasons.
let _view: id = msg_send![*controller, view];
let url_string = NSString::alloc(nil).init_str(url);
let u: id = msg_send![class!(NSURL), URLWithString:url_string];
let request: id = msg_send![class!(NSURLRequest), requestWithURL:u];
let webview: id = *controller.get_ivar(WEBVIEW_VAR);
let _: () = msg_send![webview, loadRequest:request];
}
}
}
}
#[derive(Default)]
pub struct WebView(Rc<RefCell<WebViewInner>>);
impl WebView {
pub fn configure<T: ViewController + WebViewController + 'static>(&self, controller: &T) {
{
let mut webview = self.0.borrow_mut();
webview.configure(controller);
}
//controller.did_load();
}
pub fn get_handle(&self) -> Option<ShareId<Object>> {
let webview = self.0.borrow();
webview.controller.clone()
}
pub fn load_url(&self, url: &str) {
let webview = self.0.borrow();
webview.load_url(url);
}
pub fn add_user_script(&self, script: &str, inject_at: InjectAt, main_frame_only: bool) {
let webview = self.0.borrow();
webview.add_user_script(script, inject_at, main_frame_only);
}
pub fn add_handler(&self, name: &str) {
let webview = self.0.borrow();
webview.add_handler(name);
}
}

View file

@ -1,54 +0,0 @@
//! Implements an `NSWindow` wrapper for MacOS, backed by
//! Cocoa and associated widgets. This also handles looping back
//! lifecycle events, such as window resizing or close events.
use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NO, NSUInteger, CGRect};
use crate::geometry::Rect;
#[allow(non_upper_case_globals, non_snake_case)]
pub mod WindowStyle {
use crate::foundation::NSUInteger;
pub const Borderless: NSUInteger = 0;
pub const Titled: NSUInteger = 1 << 0;
pub const Closable: NSUInteger = 1 << 1;
pub const Miniaturizable: NSUInteger = 1 << 2;
pub const Resizable: NSUInteger = 1 << 3;
pub const UnifiedTitleAndToolbar: NSUInteger = 1 << 12;
pub const FullScreen: NSUInteger = 1 << 14;
pub const FullSizeContentView: NSUInteger = 1 << 15;
pub const Utility: NSUInteger = 1 << 4;
pub const DocModalWindow: NSUInteger = 1 << 6;
pub const NonActivatingPanel: NSUInteger = 1 << 7;
pub const HUDWindow: NSUInteger = 1 << 13;
}
pub struct WindowConfig(pub Id<Object>);
impl Default for WindowConfig {
fn default() -> Self {
WindowConfig(unsafe {
let dimensions: CGRect = Rect::new(0., 0., 800., 600.).into();
let style = WindowStyle::Resizable | WindowStyle::Miniaturizable | WindowStyle::UnifiedTitleAndToolbar |
WindowStyle::Closable | WindowStyle::Titled;
let alloc: id = msg_send![class!(NSWindow), alloc];
let window: id = msg_send![alloc, initWithContentRect:dimensions styleMask:style backing:2 as NSUInteger defer:YES];
let _: () = msg_send![window, autorelease];
let _: () = msg_send![window, setTitlebarAppearsTransparent:NO];
// This is very important! NSWindow is an old class and has some behavior that we need
// to disable, like... this. If we don't set this, we'll segfault entirely because the
// Objective-C runtime gets out of sync.
let _: () = msg_send![window, setReleasedWhenClosed:NO];
Id::from_ptr(window)
})
}
}

View file

@ -1,52 +0,0 @@
//! Everything useful for the `WindowController`. Handles injecting an `NSWindowController` subclass
//! into the Objective C runtime, which loops back to give us lifecycle methods.
use std::rc::Rc;
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::foundation::id;
use crate::constants::WINDOW_CONTROLLER_PTR;
use crate::utils::load;
use crate::window::WindowController;
/// Called when an `NSWindow` receives a `windowWillClose:` event.
/// Good place to clean up memory and what not.
extern fn will_close<T: WindowController>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_CONTROLLER_PTR);
{
let window = window.borrow();
(*window).will_close();
}
Rc::into_raw(window);
}
/// Injects an `NSWindowController` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_controller_class<T: WindowController + 'static>() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSWindowController);
let mut decl = ClassDecl::new("RSTWindowController", superclass).unwrap();
decl.add_ivar::<usize>(WINDOW_CONTROLLER_PTR);
// Subclassed methods
// NSWindowDelegate methods
decl.add_method(sel!(windowWillClose:), will_close::<T> as extern fn(&Object, _, _));
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
}

View file

@ -1,15 +0,0 @@
//! Enums used in Window construction and handling.
pub enum WindowTitleVisibility {
Visible,
Hidden
}
impl From<WindowTitleVisibility> for usize {
fn from(visibility: WindowTitleVisibility) -> usize {
match visibility {
WindowTitleVisibility::Visible => 0,
WindowTitleVisibility::Hidden => 1
}
}
}

View file

@ -1,134 +0,0 @@
//! Implements `WindowHandle`, which wraps a lower-level `NSWindowController` and handles method
//! shuffling to call through to the window it holds.
//!
//! We use `NSWindowController` as it has lifecycle methods that are useful, in addition to the
//! standard `NSWindowDelegate` methods.
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::{id, nil, YES, NO, CGSize, NSString};
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
#[derive(Debug, Default, Clone)]
pub struct WindowHandle(pub Option<ShareId<Object>>);
impl WindowHandle {
/// Handles setting the title on the underlying window. Allocates and passes an `NSString` over
/// to the Objective C runtime.
pub fn set_title(&self, title: &str) {
if let Some(controller) = &self.0 {
unsafe {
let title = NSString::new(title);
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setTitle:title];
}
}
}
/// Sets the title visibility for the underlying window.
pub fn set_title_visibility(&self, visibility: usize) {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setTitleVisibility:visibility];
}
}
}
/// Used for configuring whether the window is movable via the background.
pub fn set_movable_by_background(&self, movable: bool) {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setMovableByWindowBackground:match movable {
true => YES,
false => NO
}];
}
}
}
/// Used for setting whether this titlebar appears transparent.
pub fn set_titlebar_appears_transparent(&self, transparent: bool) {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setTitlebarAppearsTransparent:match transparent {
true => YES,
false => NO
}];
}
}
}
/// Used for setting this Window autosave name.
pub fn set_autosave_name(&mut self, name: &str) {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let autosave = NSString::new(name);
let _: () = msg_send![window, setFrameAutosaveName:autosave];
}
}
}
/// Sets the minimum size this window can shrink to.
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
if let Some(controller) = &self.0 {
unsafe {
let size = CGSize::new(width.into(), height.into());
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setMinSize:size];
}
}
}
/// Used for setting a toolbar on this window.
pub fn set_toolbar<TC: ToolbarController>(&self, toolbar: &Toolbar<TC>) {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setToolbar:&*toolbar.objc_controller.0];
}
}
}
/// Used for setting the content view controller for this window.
pub fn set_content_view_controller<T: Layout + 'static>(&self, view_controller: &T) {
if let Some(controller) = &self.0 {
unsafe {
if let Some(vc) = view_controller.get_backing_node() {
let _: () = msg_send![*controller, setContentViewController:&*vc];
}
}
}
}
/// On macOS, calling `show()` is equivalent to calling `makeKeyAndOrderFront`. This is the
/// most common use case, hence why this method was chosen - if you want or need something
/// else, feel free to open an issue to discuss.
///
/// You should never be calling this yourself, mind you - Alchemy core handles this for you.
pub fn show(&self) {
if let Some(controller) = &self.0 {
unsafe {
let _: () = msg_send![*controller, showWindow:nil];
}
}
}
/// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the
/// window.
///
/// I dunno what else to say here, lol.
pub fn close(&self) {
if let Some(controller) = &self.0 {
unsafe {
let _: () = msg_send![*controller, close];
}
}
}
}

View file

@ -1,166 +0,0 @@
//! Implements Window controls for macOS, by wrapping the various necessary moving pieces
//! (`NSWindow`, `NSWindowController`, and `NSWindowDelegate`) into one trait that you can
//! implement.
//!
//! For example:
//!
//! ```
//! use appkit::prelude::{AppController, Window};
//! use window::MyWindow;
//!
//! pub struct MyApp(Window<MyWindow>);
//!
//! impl MyApp {
//! pub fn new() -> Self {
//! MyApp(Window::new(MyWindow {
//! // Your things here
//! }))
//! }
//! }
//!
//! impl AppController for MyApp {
//! fn did_load(&self) {
//! self.0.show();
//! }
//! }
//! ```
//!
//! This simulate class-based structures well enough - you just can't subclass. Now you can do the
//! following:
//!
//! ```
//! use appkit::prelude::{WindowController, WindowHandle};
//!
//! pub struct MyWindow;
//!
//! impl WindowController for MyWindow {
//! fn did_load(&mut self, handle: WindowHandle) {}
//! }
//! ```
use std::rc::Rc;
use std::cell::RefCell;
use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::foundation::{id, nil};
use crate::constants::WINDOW_CONTROLLER_PTR;
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
mod controller;
use controller::register_window_controller_class;
pub mod enums;
pub use enums::{WindowTitleVisibility};
pub mod config;
pub use config::{WindowConfig, WindowStyle};
pub mod handle;
pub use handle::WindowHandle;
pub mod traits;
pub use traits::WindowController;
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
#[derive(Clone, Debug)]
pub struct Window<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: WindowHandle,
pub controller: Rc<RefCell<T>>
}
impl<T> Window<T> where T: WindowController + 'static {
/// Allocates and configures a `WindowController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied controller.
///
/// Now, you may look at this and go "hey, the hell is going on here - why don't you make the
/// `NSWindow` in `[NSWindowController loadWindow]`?
///
/// This is a great question. It's because NSWindowController is... well, broken or buggy -
/// pick a term, either works. It's optimized for loading from xib/nib files, and attempting to
/// get loadWindow to fire properly is a pain in the rear (you're fighting a black box).
///
/// This is why we do this work here, but for things subclassing `NSViewController`, we go with
/// the route of implementing `loadView`.
///
/// APPKIT!
pub fn new(controller: T) -> Self {
let window = controller.config().0;
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let inner = unsafe {
let window_controller_class = register_window_controller_class::<T>();
let controller_alloc: id = msg_send![window_controller_class, alloc];
let controller: id = msg_send![controller_alloc, initWithWindow:window];
(&mut *controller).set_ivar(WINDOW_CONTROLLER_PTR, internal_callback_ptr as usize);
let window: id = msg_send![controller, window];
let _: () = msg_send![window, setDelegate:controller];
ShareId::from_ptr(controller)
};
{
let mut vc = controller.borrow_mut();
(*vc).did_load(WindowHandle(Some(inner.clone())));
}
Window {
internal_callback_ptr: internal_callback_ptr,
objc_controller: WindowHandle(Some(inner)),
controller: controller
}
}
/// Sets the title for this window.
pub fn set_title(&self, title: &str) {
self.objc_controller.set_title(title.into());
}
/// Sets the toolbar for this window.
pub fn set_toolbar<TC: ToolbarController + 'static>(&self, toolbar: &Toolbar<TC>) {
self.objc_controller.set_toolbar(toolbar);
}
/// Sets the content view controller for the window.
pub fn set_content_view_controller<VC: Layout + 'static>(&self, view_controller: &VC) {
self.objc_controller.set_content_view_controller(view_controller);
}
/// Shows the window, running a configuration pass if necessary.
pub fn show(&self) {
self.objc_controller.show();
}
/// Closes the window.
pub fn close(&self) {
self.objc_controller.close();
}
}
impl<T> Drop for Window<T> {
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
/// safer than sorry.
///
/// We also clean up our loopback pointer that we use for callbacks.
fn drop(&mut self) {
unsafe {
if let Some(objc_controller) = &self.objc_controller.0 {
let window: id = msg_send![*objc_controller, window];
let _: () = msg_send![window, setDelegate:nil];
}
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}

View file

@ -1,118 +0,0 @@
//! Implements an `NSWindow` wrapper for MacOS, backed by Cocoa and associated widgets. This also handles looping back
//! lifecycle events, such as window resizing or close events.
use std::rc::Rc;
use std::cell::RefCell;
use cocoa::base::{id, nil};
use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::constants::WINDOW_CONTROLLER_PTR;
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
use crate::window::handle::WindowHandle;
use crate::window::traits::WindowController;
use crate::window::controller::register_window_controller_class;
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
#[derive(Clone, Debug)]
pub struct Window<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: WindowHandle,
pub controller: Rc<RefCell<T>>
}
impl<T> Window<T> where T: WindowController + 'static {
/// Allocates and configures a `WindowController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied controller.
///
/// Now, you may look at this and go "hey, the hell is going on here - why don't you make the
/// `NSWindow` in `[NSWindowController loadWindow]`?
///
/// This is a great question. It's because NSWindowController is... well, broken or buggy -
/// pick a term, either works. It's optimized for loading from xib/nib files, and attempting to
/// get loadWindow to fire properly is a pain in the rear (you're fighting a black box).
///
/// This is why we do this work here, but for things subclassing `NSViewController`, we go with
/// the route of implementing `loadView`.
///
/// APPKIT!
pub fn new(controller: T) -> Self {
let window = controller.config().0;
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let inner = unsafe {
let window_controller_class = register_window_controller_class::<T>();
let controller_alloc: id = msg_send![window_controller_class, alloc];
let controller: id = msg_send![controller_alloc, initWithWindow:window];
(&mut *controller).set_ivar(WINDOW_CONTROLLER_PTR, internal_callback_ptr as usize);
let window: id = msg_send![controller, window];
let _: () = msg_send![window, setDelegate:controller];
ShareId::from_ptr(controller)
};
{
let mut vc = controller.borrow_mut();
(*vc).did_load(WindowHandle(Some(inner.clone())));
}
Window {
internal_callback_ptr: internal_callback_ptr,
objc_controller: WindowHandle(Some(inner)),
controller: controller
}
}
/// Sets the title for this window.
pub fn set_title(&self, title: &str) {
self.objc_controller.set_title(title.into());
}
/// Sets the toolbar for this window.
pub fn set_toolbar<TC: ToolbarController + 'static>(&self, toolbar: &Toolbar<TC>) {
self.objc_controller.set_toolbar(toolbar);
}
/// Sets the content view controller for the window.
pub fn set_content_view_controller<VC: Layout + 'static>(&self, view_controller: &VC) {
self.objc_controller.set_content_view_controller(view_controller);
}
/// Shows the window, running a configuration pass if necessary.
pub fn show(&self) {
self.objc_controller.show();
}
/// Closes the window.
pub fn close(&self) {
self.objc_controller.close();
}
}
impl<T> Drop for Window<T> {
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
/// safer than sorry.
///
/// We also clean up our loopback pointer that we use for callbacks.
fn drop(&mut self) {
unsafe {
if let Some(objc_controller) = &self.objc_controller.0 {
let window: id = msg_send![*objc_controller, window];
let _: () = msg_send![window, setDelegate:nil];
}
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}

View file

@ -12,13 +12,13 @@ use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{msg_send, sel, sel_impl};
use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id;
use crate::foundation::{id, nil, YES, NO, NSUInteger};
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
use crate::constants::{BACKGROUND_COLOR, VIEW_DELEGATE_PTR};
use crate::dragdrop::DragInfo;
use crate::view::traits::ViewController;
use crate::view::traits::ViewDelegate;
use crate::utils::load;
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
@ -27,6 +27,7 @@ extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
}
/// Used for handling background colors in layer backed views (which is the default here).
/// @TODO: This could be more efficient, I think.
extern fn update_layer(this: &Object, _: Sel) {
unsafe {
let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
@ -39,8 +40,8 @@ extern fn update_layer(this: &Object, _: Sel) {
}
/// Called when a drag/drop operation has entered this view.
extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
extern fn dragging_entered<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
let view = load::<T>(this, VIEW_DELEGATE_PTR);
let response = {
let v = view.borrow();
@ -55,8 +56,8 @@ extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, info: i
}
/// Called when a drag/drop operation has entered this view.
extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
extern fn prepare_for_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
let view = load::<T>(this, VIEW_DELEGATE_PTR);
let response = {
let v = view.borrow();
@ -74,8 +75,8 @@ extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Se
}
/// Called when a drag/drop operation has entered this view.
extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
extern fn perform_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
let view = load::<T>(this, VIEW_DELEGATE_PTR);
let response = {
let v = view.borrow();
@ -93,8 +94,8 @@ extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, i
}
/// Called when a drag/drop operation has entered this view.
extern fn conclude_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
extern fn conclude_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, VIEW_DELEGATE_PTR);
{
let v = view.borrow();
@ -107,8 +108,8 @@ extern fn conclude_drag_operation<T: ViewController>(this: &mut Object, _: Sel,
}
/// Called when a drag/drop operation has entered this view.
extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
extern fn dragging_exited<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, VIEW_DELEGATE_PTR);
{
let v = view.borrow();
@ -120,19 +121,41 @@ extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, info: id
Rc::into_raw(view);
}
/// Injects an `NSView` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_view_class<T: ViewController>() -> *const Class {
/// 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::get("NSView").unwrap();
let superclass = class!(NSView);
let mut decl = ClassDecl::new("RSTView", superclass).unwrap();
decl.add_ivar::<id>(BACKGROUND_COLOR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _));
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_CONTROLLER_PTR);
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
decl.add_ivar::<id>(BACKGROUND_COLOR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);

View file

@ -0,0 +1,83 @@
//! Hoists a basic `NSViewController`.
use std::rc::Rc;
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::constants::VIEW_DELEGATE_PTR;
use crate::view::traits::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);
{
let vc = controller.borrow();
(*vc).will_appear();
}
Rc::into_raw(controller);
}
/// 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);
{
let vc = controller.borrow();
(*vc).did_appear();
}
Rc::into_raw(controller);
}
/// 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);
{
let vc = controller.borrow();
(*vc).will_disappear();
}
Rc::into_raw(controller);
}
/// 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);
{
let vc = controller.borrow();
(*vc).did_disappear();
}
Rc::into_raw(controller);
}
/// 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

@ -0,0 +1,39 @@
use std::rc::Rc;
use std::cell::RefCell;
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, NO};
use crate::constants::VIEW_DELEGATE_PTR;
use crate::layout::{Layout};
use crate::view::traits::ViewDelegate;
mod class;
use class::register_view_controller_class;
//#[derive(Debug)]
pub struct ViewController {
pub objc: ShareId<Object>,
pub view: Box<ViewDelegate>
}
impl ViewController {
pub fn new<T: ViewDelegate + Layout + 'static>(view: T) -> Self {
let view = Box::new(view);
let objc = unsafe {
let vc: id = msg_send![register_view_controller_class::<T>(), new];
let _: () = msg_send![vc, setView:&*view.get_backing_node()];
let ptr: *const T = &*view;
(&mut *vc).set_ivar(VIEW_DELEGATE_PTR, ptr as usize);
ShareId::from_ptr(vc)
};
ViewController {
objc: objc,
view: view
}
}
}

207
appkit/view/mod.rs Normal file
View file

@ -0,0 +1,207 @@
//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your
//! `ViewController::did_load()` method. This method is safe to store and use, however as it's
//! UI-specific it's not thread safe.
//!
//! You can use this struct to configure how a view should look and layout. It implements
//! AutoLayout - for more information, see the AutoLayout tutorial.
use std::rc::Rc;
use std::cell::RefCell;
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NO, NSArray, NSString};
use crate::color::Color;
use crate::constants::{BACKGROUND_COLOR, VIEW_DELEGATE_PTR};
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
mod class;
use class::{register_view_class, register_view_class_with_delegate};
pub mod controller;
pub use controller::ViewController;
pub mod traits;
pub use traits::ViewDelegate;
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
/// side anyway.
#[derive(Debug)]
pub struct View<T = ()> {
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
/// An internal callback pointer that we use in delegate loopbacks. Default implementations
/// don't require this.
internal_callback_ptr: Option<*const RefCell<T>>,
/// A pointer to the delegate for this view.
pub delegate: Option<Rc<RefCell<T>>>,
/// A pointer to the Objective-C runtime top layout constraint.
pub top: LayoutAnchorY,
/// A pointer to the Objective-C runtime leading layout constraint.
pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint.
pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint.
pub bottom: LayoutAnchorY,
/// A pointer to the Objective-C runtime width layout constraint.
pub width: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime height layout constraint.
pub height: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime center X layout constraint.
pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint.
pub center_y: LayoutAnchorY
}
impl Default for View {
fn default() -> Self {
View::new()
}
}
impl View {
/// Returns a default `View`, suitable for
pub fn new() -> Self {
let view: id = unsafe {
let view: id = msg_send![register_view_class(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
view
};
View {
internal_callback_ptr: None,
delegate: None,
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
objc: ShareId::from_ptr(view),
}
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color(&self, color: Color) {
unsafe {
//let view: id = msg_send![*self.objc, view];
//(*view).set_ivar(BACKGROUND_COLOR, color.into_platform_specific_color());
//let _: () = msg_send![view, setNeedsDisplay:YES];
}
}
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe {
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = t.clone().into();
x.into_inner()
}).collect::<Vec<id>>().into();
let _: () = msg_send![&*self.objc, registerForDraggedTypes:types.into_inner()];
}
}
/// Given a subview, adds it to this view.
pub fn add_subview<T: Layout>(&self, subview: &T) {
/*if let Some(subview_controller) = subview.get_backing_node() {
unsafe {
let _: () = msg_send![*this, addChildViewController:&*subview_controller];
let subview: id = msg_send![&*subview_controller, view];
let view: id = msg_send![*this, view];
let _: () = msg_send![view, addSubview:subview];
}
}*/
}
}
impl<T> View<T> where T: ViewDelegate + 'static {
/// Initializes a new View with a given `ViewDelegate`. This enables you to respond to events
/// and customize the view as a module, similar to class-based systems.
pub fn with(delegate: T) -> View<T> {
let delegate = Rc::new(RefCell::new(delegate));
let internal_callback_ptr = {
let cloned = Rc::clone(&delegate);
Rc::into_raw(cloned)
};
let view = unsafe {
let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
(&mut *view).set_ivar(VIEW_DELEGATE_PTR, internal_callback_ptr as usize);
view
};
let view = View {
internal_callback_ptr: Some(internal_callback_ptr),
delegate: Some(delegate),
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
objc: ShareId::from_ptr(view),
};
{
let mut delegate = delegate.borrow_mut();
(*delegate).did_load(View {
internal_callback_ptr: None,
delegate: None,
top: view.top.clone(),
leading: view.leading.clone(),
trailing: view.trailing.clone(),
bottom: view.bottom.clone(),
width: view.width.clone(),
height: view.height.clone(),
center_x: view.center_x.clone(),
center_y: view.center_y.clone(),
objc: view.objc.clone()
});
}
view
}
}
impl Layout for View {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, _: &V) {}
}
impl<T> Drop for View<T> {
/// A bit of extra cleanup for delegate callback pointers.
fn drop(&mut self) {
if let ptr = &self.internal_callback_ptr {
unsafe {
let _ = Rc::from_raw(ptr);
}
}
}
}

View file

@ -1,16 +1,25 @@
//! Various traits used for Views.
use crate::dragdrop::{DragInfo, DragOperation};
use crate::view::ViewHandle;
use crate::view::View;
pub trait ViewController {
/// Where possible, we try to respect the lazy-ish-loading of macOS/iOS systems. This hook
/// notifies you when the `View` has actually loaded into memory, and you're free to do things
/// with it.
///
/// Note that you can trigger the view to load earlier, if need be... and in many cases it's
/// fine. This is a platform-specific detail you may want to read up on. :)
fn did_load(&mut self, _view: ViewHandle) {}
pub trait ViewDelegate {
/// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
/// main thread!
fn did_load(&mut self, _view: View) {}
/// Called when this is about to be added to the view heirarchy.
fn will_appear(&self) {}
/// Called after this has been added to the view heirarchy.
fn did_appear(&self) {}
/// Called when this is about to be removed from the view heirarchy.
fn will_disappear(&self) {}
/// Called when this has been removed from the view heirarchy.
fn did_disappear(&self) {}
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }

View file

@ -1,36 +1,11 @@
//! Implements wrappers around `WKNavigationAction` and `WKNavigationActionPolicy`.
use cocoa::base::{id, YES, NO};
use cocoa::foundation::NSInteger;
use objc::runtime::BOOL;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, BOOL, YES, NO, NSInteger};
use crate::networking::URLRequest;
pub enum NavigationType {
LinkActivated,
FormSubmitted,
BackForward,
Reload,
FormResubmitted,
Other
}
impl From<NSInteger> for NavigationType {
fn from(i: NSInteger) -> Self {
match i {
-1 => NavigationType::Other,
0 => NavigationType::LinkActivated,
1 => NavigationType::FormSubmitted,
2 => NavigationType::BackForward,
3 => NavigationType::Reload,
4 => NavigationType::FormResubmitted,
e => { panic!("Unsupported navigation type: {}", e); }
}
}
}
#[derive(Debug)]
pub struct NavigationAction {
pub navigation_type: NavigationType,
pub request: URLRequest
@ -51,20 +26,7 @@ impl NavigationAction {
}
}
pub enum NavigationPolicy {
Cancel,
Allow
}
impl Into<NSInteger> for NavigationPolicy {
fn into(self) -> NSInteger {
match self {
NavigationPolicy::Cancel => 0,
NavigationPolicy::Allow => 1
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct NavigationResponse {
pub can_show_mime_type: bool
}
@ -80,25 +42,8 @@ impl NavigationResponse {
}
}
pub enum NavigationResponsePolicy {
Cancel = 0,
Allow = 1,
// This is a private API!
BecomeDownload = 2
}
impl Into<NSInteger> for NavigationResponsePolicy {
fn into(self) -> NSInteger {
match self {
NavigationResponsePolicy::Cancel => 0,
NavigationResponsePolicy::Allow => 1,
NavigationResponsePolicy::BecomeDownload => 2
}
}
}
#[derive(Debug, Default)]
#[derive(Copy, Clone, Debug, Default)]
pub struct OpenPanelParameters {
pub allows_directories: bool,
pub allows_multiple_selection: bool

66
appkit/webview/config.rs Normal file
View file

@ -0,0 +1,66 @@
//! A wrapper for `WKWebViewConfiguration`. It aims to (mostly) cover
//! the important pieces of configuring and updating a WebView configuration.
use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NSString};
use crate::webview::enums::InjectAt;
/// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime
/// where everything lives.
pub struct WebViewConfig {
pub objc: Id<Object>,
pub handlers: Vec<String>
}
impl Default for WebViewConfig {
/// Initializes a default `WebViewConfig`.
fn default() -> Self {
let config = unsafe {
let config: id = msg_send![class!(WKWebViewConfiguration), new];
Id::from_ptr(config)
};
WebViewConfig {
objc: config,
handlers: vec![]
}
}
}
impl WebViewConfig {
/// Pushes the specified handler name onto the stack, queuing it for initialization with the
/// `WKWebView`.
pub fn add_handler(&mut self, name: &str) {
self.handlers.push(name.to_string());
}
/// Adds the given user script to the underlying `WKWebView` user content controller.
pub fn add_user_script(&mut self, script: &str, at: InjectAt, main_frame_only: bool) {
let source = NSString::new(script);
unsafe {
let alloc: id = msg_send![class!(WKUserScript), alloc];
let user_script: id = msg_send![alloc, initWithSource:source injectionTime:inject_at forMainFrameOnly:match main_frame_only {
true => YES,
false => NO
}];
let content_controller: id = msg_send![&*self.objc, userContentController];
let _: () = msg_send![content_controller, addUserScript:user_script];
}
}
/// Enables access to the underlying inspector view for `WKWebView`.
pub fn enable_developer_extras(&mut self) {
let key = NSString::new("developerExtrasEnabled");
unsafe {
let yes: id = msg_send![class!(NSNumber), numberWithBool:YES];
let preferences: id = msg_send![&*self.objc, preferences];
let _: () = msg_send![preferences, setValue:yes forKey:key];
}
}
}

View file

@ -7,18 +7,16 @@ use std::ffi::c_void;
use block::Block;
use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSArray, NSInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, CGRect, NSString, NSArray, NSInteger};
use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
use crate::geometry::Rect;
use crate::view::traits::ViewController;
use crate::webview::action::{NavigationAction, NavigationResponse};
use crate::webview::traits::WebViewController;
use crate::utils::str_from;
/// Loads and configures ye old WKWebView/View for this controller.
extern fn load_view<T: ViewController + WebViewController>(this: &mut Object, _: Sel) {
@ -26,12 +24,12 @@ extern fn load_view<T: ViewController + WebViewController>(this: &mut Object, _:
let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR);
// Technically private!
#[cfg(feature = "enable-webview-downloading")]
#[cfg(feature = "webview-downloading")]
let process_pool: id = msg_send![configuration, processPool];
#[cfg(feature = "enable-webview-downloading")]
#[cfg(feature = "webview-downloading")]
let _: () = msg_send![process_pool, _setDownloadDelegate:&*this];
let zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(1000., 600.));
let zero: CGRect = Rect::zero().into();
let webview_alloc: id = msg_send![class!(WKWebView), alloc];
let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration];
let _: () = msg_send![webview, setWantsLayer:YES];
@ -59,7 +57,7 @@ extern fn view_did_load<T: ViewController + WebViewController>(this: &Object, _:
/// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your
/// `WebViewController`, where you should handle the event.
extern fn alert<T: WebViewController + 'static>(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) {
let alert = str_from(s);
let alert = NSString::wrap(s).to_str();
println!("Alert: {}", alert);
// @TODO: This is technically (I think?) a private method, and there's some other dance that
@ -85,8 +83,8 @@ extern fn alert<T: WebViewController + 'static>(_: &Object, _: Sel, _: id, s: id
/// Fires when a message has been passed from the underlying `WKWebView`.
extern fn on_message<T: WebViewController + 'static>(this: &Object, _: Sel, _: id, script_message: id) {
unsafe {
let name = str_from(msg_send![script_message, name]);
let body = str_from(msg_send![script_message, body]);
let name = NSString::wrap(msg_send![script_message, name]).to_str();
let body = NSString::wrap(msg_send![script_message, body]).to_str();
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR);
let webview = ptr as *const T;
@ -140,13 +138,12 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
match urls {
Some(u) => {
let nsurls: Vec<id> = u.iter().map(|s| {
let s = NSString::alloc(nil).init_str(&s);
let nsurls: NSArray = u.iter().map(|s| {
let s = NSString::new(&s);
msg_send![class!(NSURL), URLWithString:s]
}).collect();
}).collect::<Vec<id>>().into();
let array = NSArray::arrayWithObjects(nil, &nsurls);
(*handler).call((array,));
(*handler).call((nsurls.into_inner(),));
},
None => { (*handler).call((nil,)); }
@ -157,7 +154,7 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
/// Called when a download has been initiated in the WebView, and when the navigation policy
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
/// API.
#[cfg(feature = "enable-webview-downloading")]
#[cfg(feature = "webview-downloading")]
extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) {
let webview = unsafe {
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR);
@ -166,19 +163,19 @@ extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel,
};
let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>;
let filename = str_from(suggested_filename);
let filename = NSString::wrap(suggested_filename).to_str();
webview.run_save_panel(filename, move |can_overwrite, path| unsafe {
if path.is_none() {
let _: () = msg_send![download, cancel];
}
let path = NSString::alloc(nil).init_str(&path.unwrap());
let path = NSString::new(&path.unwrap());
(*handler).call((match can_overwrite {
true => YES,
false => NO
}, path));
}, path.into_inner()));
});
}
@ -218,7 +215,7 @@ pub fn register_controller_class<
// WKDownloadDelegate is a private class on macOS that handles downloading (saving) files.
// It's absurd that this is still private in 2020. This probably couldn't get into the app
// store, so... screw it, fine for now.
#[cfg(feature = "enable-webview-downloading")]
#[cfg(feature = "webview-downloading")]
decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize));
VIEW_CLASS = decl.register();

100
appkit/webview/enums.rs Normal file
View file

@ -0,0 +1,100 @@
//! Various enums used throughout the `webview` module.
use crate::foundation::NSInteger;
/// Describes a navigation type from within the `WebView`.
pub enum NavigationType {
/// A user activated a link.
LinkActivated,
/// A user submitted a form.
FormSubmitted,
/// A user went backwards or fowards.
BackForward,
/// A user reloaded the webview.
Reload,
/// A user resubmitted a form.
FormResubmitted,
/// Other.
Other
}
impl From<NSInteger> for NavigationType {
fn from(i: NSInteger) -> Self {
match i {
-1 => NavigationType::Other,
0 => NavigationType::LinkActivated,
1 => NavigationType::FormSubmitted,
2 => NavigationType::BackForward,
3 => NavigationType::Reload,
4 => NavigationType::FormResubmitted,
e => { panic!("Unsupported navigation type: {}", e); }
}
}
}
/// Describes the policy for a given navigation.
pub enum NavigationPolicy {
/// Should be canceled.
Cancel,
/// Allowed.
Allow
}
impl From<NavigationPolicy> for NSInteger {
fn into(self) -> Self {
match self {
NavigationPolicy::Cancel => 0,
NavigationPolicy::Allow => 1
}
}
}
/// Describes a response policy for a given navigation.
pub enum NavigationResponsePolicy {
/// Should be canceled.
Cancel,
/// Allowed.
Allow,
/// This is a private API, and likely won't make it into the App Store. Will only be available
/// if you opt in via the `webview-downloading` feature.
#[cfg(feature = "webview-downloading")]
BecomeDownload
}
impl From<NavigationResponsePolicy> for NSInteger {
fn into(self) -> Self {
match self {
NavigationResponsePolicy::Cancel => 0,
NavigationResponsePolicy::Allow => 1,
#[cfg(feature = "webview-downloading")]
NavigationResponsePolicy::BecomeDownload => 2
}
}
}
/// Dictates where a given user script should be injected.
pub enum InjectAt {
/// Inject at the start of the document.
Start = 0,
/// Inject at the end of the document.
End = 1
}
impl From<InjectAt> for NSInteger {
fn from(at: InjectAt) -> Self {
match at {
InjectAt::Start => 0,
InjectAt::End => 1
}
}
}

View file

@ -1,5 +1,5 @@
//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your
//! `ViewController::did_load()` method. This method is safe to store and use, however as it's
//! A `WebViewHandle` represents an underlying `WKWebView`. You're passed a reference to one during your
//! `WebViewController::did_load()` method. This method is safe to store and use, however as it's
//! UI-specific it's not thread safe.
//!
//! You can use this struct to configure how a view should look and layout. It implements
@ -16,10 +16,10 @@ use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}
use crate::pasteboard::PasteboardType;
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
/// instead of a stock `WKWebView` for easier recordkeeping, since it'll need to hold the `WKWebView` on that
/// side anyway.
#[derive(Debug, Default, Clone)]
pub struct ViewHandle {
pub struct WebViewHandle {
/// A pointer to the Objective-C runtime view controller.
pub objc: Option<ShareId<Object>>,
@ -48,13 +48,13 @@ pub struct ViewHandle {
pub center_y: LayoutAnchorY
}
impl ViewHandle {
impl WebViewHandle {
pub(crate) fn new(object: ShareId<Object>) -> Self {
let view: id = unsafe {
msg_send![&*object, view]
};
ViewHandle {
WebViewHandle {
objc: Some(object),
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
@ -66,46 +66,4 @@ impl ViewHandle {
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
}
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color(&self, color: Color) {
if let Some(objc) = &self.objc {
unsafe {
let view: id = msg_send![*objc, view];
(*view).set_ivar(BACKGROUND_COLOR, color.into_platform_specific_color());
let _: () = msg_send![view, setNeedsDisplay:YES];
}
}
}
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
if let Some(objc) = &self.objc {
unsafe {
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = t.clone().into();
x.into_inner()
}).collect::<Vec<id>>().into();
let view: id = msg_send![*objc, view];
let _: () = msg_send![view, registerForDraggedTypes:types.into_inner()];
}
}
}
pub fn add_subview<T: Layout>(&self, subview: &T) {
if let Some(this) = &self.objc {
if let Some(subview_controller) = subview.get_backing_node() {
unsafe {
let _: () = msg_send![*this, addChildViewController:&*subview_controller];
let subview: id = msg_send![&*subview_controller, view];
let view: id = msg_send![*this, view];
let _: () = msg_send![view, addSubview:subview];
}
}
}
}
}

View file

@ -1,70 +1,48 @@
//! This module implements tooling for constructing Views. Notably, it provides the following:
//! Implements a WebView, which wraps a number of different classes/delegates/controllers into one
//! useful interface. This encompasses...
//!
//! - A `View` type, which holds your `impl ViewController` and handles routing around platform
//! lifecycle methods accordingly. This is a heap allocation.
//! - A `ViewController` trait, which enables you to hook into the `NSViewController` lifecycle
//! methods.
//! - A `ViewHandle` struct, which wraps a platform-provided `NSView` and enables you to configure
//! things such as appearance and layout.
//!
//! You can use it like the following:
//!
//! ```
//! use appkit::prelude::{View, ViewController, ViewHandle};
//! use appkit::color::rgb;
//!
//! #[derive(Default)]
//! pub struct MyView {
//! pub view: ViewHandle
//! }
//!
//! impl ViewController for MyView {
//! fn did_load(&mut self, view: ViewHandle) {
//! self.view = view;
//! self.view.set_background_color(rgb(0, 0, 0));
//! }
//! }
//! ```
//!
//! For more information and examples, consult the sample code distributed in the git repository.
//! - `WKWebView`
//! - `WKUIDelegate`
//! - `WKScriptMessageHandler`
//! - `NSViewController`
use std::rc::Rc;
use std::cell::RefCell;
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::id;
use crate::color::Color;
use crate::constants::VIEW_CONTROLLER_PTR;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::foundation::{id, nil, YES, NO, NSString};
use crate::constants::WEBVIEW_CONTROLLER_PTR;
use crate::webview::controller::register_controller_class;
mod class;
mod controller;
use controller::register_controller_class;
pub mod actions;
pub(crate) mod controller;
//pub(crate) mod process_pool;
pub mod traits;
pub use traits::ViewController;
pub use traits::WebViewController;
pub mod handle;
pub use handle::ViewHandle;
pub mod config;
pub use config::WebViewConfig;
/// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards
/// calls into your supplied `ViewController` trait object. This involves heap allocation, but all
/// of Cocoa is essentially Heap'd, so... well, enjoy.
#[derive(Clone)]
pub struct View<T> {
pub struct WebView<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: ViewHandle,
pub objc_controller: WebViewHandle,
pub controller: Rc<RefCell<T>>
}
impl<T> View<T> where T: ViewController + 'static {
impl<T> WebView<T> where T: WebViewController + 'static {
/// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied view controller.
pub fn new(controller: T) -> Self {
let config = controller.config();
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
@ -72,24 +50,26 @@ impl<T> View<T> where T: ViewController + 'static {
Rc::into_raw(cloned)
};
let inner = unsafe {
let handle = WebViewHandle::new(unsafe {
let view_controller: id = msg_send![register_controller_class::<T>(), new];
(&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
(&mut *view_controller).set_ivar(WEBVIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
let view: id = msg_send![view_controller, view];
(&mut *view).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
// WKWebView isn't really great to subclass, so we don't bother here unlike other
// widgets in this framework. Just set and forget.
let frame: CGRect = Rect::zero().into();
let alloc: id = msg_send![class!(WKWebView), alloc];
let view: id = msg_send![alloc, initWithFrame:frame configuration:&*config.0];
let _: () = msg_send![&*view_controller, setView:view];
ShareId::from_ptr(view_controller)
};
let handle = ViewHandle::new(inner);
});
{
let mut vc = controller.borrow_mut();
(*vc).did_load(handle.clone());
}
View {
WebView {
internal_callback_ptr: internal_callback_ptr,
objc_controller: handle,
controller: controller

View file

@ -2,14 +2,40 @@
//! `WKWebView`. It allows you to do things such as handle opening a file (for uploads or
//! in-browser-processing), handling navigation actions or JS message callbacks, and so on.
use crate::webview::action::{
use crate::webview::config::WebViewConfig;
use crate::webview::enums::{
NavigationAction, NavigationPolicy,
NavigationResponse, NavigationResponsePolicy,
OpenPanelParameters
};
use crate::webview::handle::WebViewHandle;
/// You can implement this on structs to handle callbacks from the underlying `WKWebView`.
pub trait WebViewController {
/// Due to a quirk in how the underlying `WKWebView` works, the configuration object must be
/// set up before initializing anything. To enable this, you can implement this method and
/// return whatever `WebViewConfig` object you want.
///
/// By default, this returns `WebViewConfig::default()`.
fn configure(&mut self) -> WebViewConfig { WebViewConfig::default() }
/// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
/// main thread!
fn did_load(&mut self, _view: WebViewHandle) {}
/// Called when this is about to be added to the view heirarchy.
fn will_appear(&self) {}
/// Called after this has been added to the view heirarchy.
fn did_appear(&self) {}
/// Called when this is about to be removed from the view heirarchy.
fn will_disappear(&self) {}
/// Called when this has been removed from the view heirarchy.
fn did_disappear(&self) {}
/// Called when a JS message is passed by the browser process. For instance, if you added
/// `notify` as a callback, and in the browser you called
/// `webkit.messageHandlers.notify.postMessage({...})` it would wind up here, with `name` being
@ -41,7 +67,7 @@ pub trait WebViewController {
/// and thread the callbacks accordingly.
///
/// Note that this specific callback is only
/// automatically fired if you're linking in to the `enable_webview_downloads` feature, which
/// automatically fired if you're linking in to the `webview_downloading` feature, which
/// is not guaranteed to be App Store compatible. If you want a version that can go in the App
/// Store, you'll likely need to write some JS in the webview to handle triggering
/// downloading/saving. This is due to Apple not allowing the private methods on `WKWebView` to

68
appkit/window/class.rs Normal file
View file

@ -0,0 +1,68 @@
//! Everything useful for the `WindowDelegate`. Handles injecting an `NSWindowDelegate` subclass
//! into the Objective C runtime, which loops back to give us lifecycle methods.
use std::rc::Rc;
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::foundation::id;
use crate::utils::load;
use crate::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
/// Called when an `NSWindow` receives a `windowWillClose:` event.
/// Good place to clean up memory and what not.
extern fn will_close<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_close();
}
Rc::into_raw(window);
}
/// Injects an `NSWindow` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_class() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSWindow);
let decl = ClassDecl::new("RSTWindow", superclass).unwrap();
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
}
/// Injects an `NSWindowDelegate` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_class_with_delegate<T: WindowDelegate>() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSWindow);
let mut decl = ClassDecl::new("RSTWindowWithDelegate", superclass).unwrap();
decl.add_ivar::<usize>(WINDOW_DELEGATE_PTR);
// Subclassed methods
// NSWindowDelegate methods
decl.add_method(sel!(windowWillClose:), will_close::<T> as extern fn(&Object, _, _));
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
}

57
appkit/window/config.rs Normal file
View file

@ -0,0 +1,57 @@
//! Certain properties of an `NSWindow` cannot be changed after initialization (e.g, the style
//! mask). This configuration object acts as a way to orchestrate enabling customization before the
//! window object is created - it's returned in your `WindowDelegate` object.
use crate::foundation::NSUInteger;
use crate::geometry::Rect;
use crate::window::enums::WindowStyle;
#[derive(Debug)]
pub struct WindowConfig {
/// The style the window should have.
pub style: NSUInteger,
/// The initial dimensions for the window.
pub initial_dimensions: Rect,
/// From the Apple docs:
///
/// _"When true, the window server defers creating the window device
/// until the window is moved onscreen. All display messages sent to
/// the window or its views are postponed until the window is created,
/// just before its moved onscreen."_
///
/// You generally just want this to be true, and it's the default for this struct.
pub defer: bool
}
impl Default for WindowConfig {
fn default() -> Self {
let mut config = WindowConfig {
style: 0,
initial_dimensions: Rect::new(100., 100., 1024., 768.),
defer: true
};
config.set_styles(&[
WindowStyle::Resizable, WindowStyle::Miniaturizable, WindowStyle::UnifiedTitleAndToolbar,
WindowStyle::Closable, WindowStyle::Titled
]);
config
}
}
impl WindowConfig {
/// Given a set of styles, converts them to `NSUInteger` and stores them for later use.
pub fn set_styles(&mut self, styles: &[WindowStyle]) {
let mut style: NSUInteger = 0;
for mask in styles {
let i: NSUInteger = mask.into();
style = style | i;
}
self.style = style;
}
}

View file

@ -0,0 +1,28 @@
//! Everything useful for the `WindowController`. Handles injecting an `NSWindowController` subclass
//! into the Objective C runtime, which loops back to give us lifecycle methods.
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::Class;
use objc::class;
use crate::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
/// Injects an `NSWindowController` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_controller_class<T: WindowDelegate>() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSWindowController);
let mut decl = ClassDecl::new("RSTWindowController", superclass).unwrap();
decl.add_ivar::<usize>(WINDOW_DELEGATE_PTR);
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
}

View file

@ -0,0 +1,88 @@
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::foundation::{id, nil};
use crate::layout::traits::Layout;
use crate::window::{Window, WindowConfig, WindowDelegate, WINDOW_DELEGATE_PTR};
mod class;
use class::register_window_controller_class;
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
pub struct WindowController<T> {
pub objc: ShareId<Object>,
pub window: Window<T>
}
impl<T> WindowController<T> where T: WindowDelegate + 'static {
/// Allocates and configures an `NSWindowController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied delegate.
pub fn with(config: WindowConfig, delegate: T) -> Self {
let mut window = Window::with(config, delegate);
let objc = unsafe {
let window_controller_class = register_window_controller_class::<T>();
let controller_alloc: id = msg_send![window_controller_class, alloc];
let controller: id = msg_send![controller_alloc, initWithWindow:&*window.objc];
if let Some(ptr) = window.internal_callback_ptr {
(&mut *controller).set_ivar(WINDOW_DELEGATE_PTR, ptr as usize);
}
ShareId::from_ptr(controller)
};
if let Some(window_delegate) = &mut window.delegate {
let mut window_delegate = window_delegate.borrow_mut();
(*window_delegate).did_load(Window {
internal_callback_ptr: None,
delegate: None,
objc: window.objc.clone()
});
}
WindowController {
objc: objc,
window: window
}
}
/// Sets the content view controller for the window.
pub fn set_content_view_controller<VC: Layout + 'static>(&self, view_controller: &VC) {
//self.objc_controller.set_content_view_controller(view_controller);
}
/// Shows the window, running a configuration pass if necessary.
pub fn show(&self) {
unsafe {
let _: () = msg_send![&*self.objc, showWindow:nil];
}
}
/// Closes the window.
pub fn close(&self) {
unsafe {
let _: () = msg_send![&*self.objc, close];
}
}
}
/*impl<T> Drop for Window<T> {
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
/// safer than sorry.
///
/// We also clean up our loopback pointer that we use for callbacks.
fn drop(&mut self) {
unsafe {
let window: id = msg_send![*objc_controller, window];
let _: () = msg_send![window, setDelegate:nil];
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}*/

98
appkit/window/enums.rs Normal file
View file

@ -0,0 +1,98 @@
//! Enums used in Window construction and handling.
use crate::foundation::NSUInteger;
/// Describes window styles that can be displayed.
pub enum WindowStyle {
/// Window has no border. You generally do not want this.
Borderless,
/// Window supports title.
Titled,
/// Window is closable.
Closable,
/// Window can be shrunk.
Miniaturizable,
/// Window can be resized.
Resizable,
/// Window does not separate area between title and toolbar.
UnifiedTitleAndToolbar,
/// Window is full screen.
FullScreen,
/// Window does not buffer content view below title/toolbar.
FullSizeContentView,
/// Utility window.
Utility,
/// Modal window for doc.
DocModalWindow,
/// Non-activating panel.
NonActivatingPanel,
/// A HUD window.
HUDWindow
}
impl From<WindowStyle> for NSUInteger {
fn from(style: WindowStyle) -> Self {
match style {
WindowStyle::Borderless => 0,
WindowStyle::Titled => 1 << 0,
WindowStyle::Closable => 1 << 1,
WindowStyle::Miniaturizable => 1 << 2,
WindowStyle::Resizable => 1 << 3,
WindowStyle::UnifiedTitleAndToolbar => 1 << 12,
WindowStyle::FullScreen => 1 << 14,
WindowStyle::FullSizeContentView => 1 << 15,
WindowStyle::Utility => 1 << 4,
WindowStyle::DocModalWindow => 1 << 6,
WindowStyle::NonActivatingPanel => 1 << 7,
WindowStyle::HUDWindow => 1 << 13
}
}
}
impl From<&WindowStyle> for NSUInteger {
fn from(style: &WindowStyle) -> Self {
match style {
WindowStyle::Borderless => 0,
WindowStyle::Titled => 1 << 0,
WindowStyle::Closable => 1 << 1,
WindowStyle::Miniaturizable => 1 << 2,
WindowStyle::Resizable => 1 << 3,
WindowStyle::UnifiedTitleAndToolbar => 1 << 12,
WindowStyle::FullScreen => 1 << 14,
WindowStyle::FullSizeContentView => 1 << 15,
WindowStyle::Utility => 1 << 4,
WindowStyle::DocModalWindow => 1 << 6,
WindowStyle::NonActivatingPanel => 1 << 7,
WindowStyle::HUDWindow => 1 << 13
}
}
}
/// Describes whether a window shows a title or not.
pub enum WindowTitleVisibility {
/// Title is visible.
Visible,
/// Title is hidden.
Hidden
}
impl From<WindowTitleVisibility> for usize {
fn from(visibility: WindowTitleVisibility) -> usize {
match visibility {
WindowTitleVisibility::Visible => 0,
WindowTitleVisibility::Hidden => 1
}
}
}

251
appkit/window/mod.rs Normal file
View file

@ -0,0 +1,251 @@
//! Implements an `NSWindow` wrapper for MacOS, backed by Cocoa and associated widgets. This also handles looping back
//! lifecycle events, such as window resizing or close events.
use std::rc::Rc;
use std::cell::RefCell;
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::{id, nil, YES, NO, NSString, NSUInteger, CGRect, CGSize};
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
mod class;
use class::{register_window_class, register_window_class_with_delegate};
pub mod config;
pub use config::WindowConfig;
pub mod controller;
pub use controller::WindowController;
pub mod enums;
pub mod traits;
pub use traits::WindowDelegate;
pub(crate) static WINDOW_DELEGATE_PTR: &str = "rstWindowDelegate";
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
#[derive(Debug)]
pub struct Window<T = ()> {
/// A pointer to the Objective-C `NSWindow`. Used in callback orchestration.
pub(crate) internal_callback_ptr: Option<*const RefCell<T>>,
/// Represents an `NSWindow` in the Objective-C runtime.
pub objc: ShareId<Object>,
/// A delegate for this window.
pub delegate: Option<Rc<RefCell<T>>>
}
impl Default for Window {
fn default() -> Self {
Window::new(WindowConfig::default())
}
}
impl Window {
/// Constructs a new Window.
pub fn new(config: WindowConfig) -> Window {
let objc = unsafe {
let alloc: id = msg_send![register_window_class(), alloc];
// Other types of backing (Retained/NonRetained) are archaic, dating back to the
// NeXTSTEP era, and are outright deprecated... so we don't allow setting them.
let buffered: NSUInteger = 2;
let dimensions: CGRect = config.initial_dimensions.into();
let window: id = msg_send![alloc, initWithContentRect:dimensions
styleMask:config.style
backing:buffered
defer:match config.defer {
true => YES,
false => NO
}
];
let _: () = msg_send![window, autorelease];
// This is very important! NSWindow is an old class and has some behavior that we need
// to disable, like... this. If we don't set this, we'll segfault entirely because the
// Objective-C runtime gets out of sync by releasing the window out from underneath of
// us.
let _: () = msg_send![window, setReleasedWhenClosed:NO];
ShareId::from_ptr(window)
};
Window {
internal_callback_ptr: None,
objc: objc,
delegate: None
}
}
}
impl<T> Window<T> {
/// Handles setting the title on the underlying window. Allocates and passes an `NSString` over
/// to the Objective C runtime.
pub fn set_title(&self, title: &str) {
unsafe {
let title = NSString::new(title);
let _: () = msg_send![&*self.objc, setTitle:title];
}
}
/// Sets the title visibility for the underlying window.
pub fn set_title_visibility(&self, visibility: usize) {
unsafe {
let _: () = msg_send![&*self.objc, setTitleVisibility:visibility];
}
}
/// Used for configuring whether the window is movable via the background.
pub fn set_movable_by_background(&self, movable: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setMovableByWindowBackground:match movable {
true => YES,
false => NO
}];
}
}
/// Used for setting whether this titlebar appears transparent.
pub fn set_titlebar_appears_transparent(&self, transparent: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setTitlebarAppearsTransparent:match transparent {
true => YES,
false => NO
}];
}
}
/// Used for setting this Window autosave name.
pub fn set_autosave_name(&self, name: &str) {
unsafe {
let autosave = NSString::new(name);
let _: () = msg_send![&*self.objc, setFrameAutosaveName:autosave];
}
}
/// Sets the minimum size this window can shrink to.
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
unsafe {
let size = CGSize::new(width.into(), height.into());
let _: () = msg_send![&*self.objc, setMinSize:size];
}
}
/// Used for setting a toolbar on this window.
pub fn set_toolbar<TC: ToolbarController>(&self, toolbar: &Toolbar<TC>) {
unsafe {
let _: () = msg_send![&*self.objc, setToolbar:&*toolbar.objc_controller.0];
}
}
/// Used for setting the content view controller for this window.
pub fn set_content_view_controller<L: Layout + 'static>(&self, view_controller: &L) {
//unsafe {
// if let Some(vc) = view_controller.get_backing_node() {
// let _: () = msg_send![*controller, setContentViewController:&*vc];
// }
//}
}
/// Shows the window.
pub fn show(&self) {
unsafe {
let _: () = msg_send![&*self.objc, makeKeyAndOrderFront:nil];
}
}
/// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the
/// window.
///
/// I dunno what else to say here, lol.
pub fn close(&self) {
unsafe {
let _: () = msg_send![&*self.objc, close];
}
}
}
impl<T> Window<T> where T: WindowDelegate + 'static {
/// Constructs a new Window.
pub fn with(config: WindowConfig, delegate: T) -> Self {
let delegate = Rc::new(RefCell::new(delegate));
let internal_callback_ptr = {
let cloned = Rc::clone(&delegate);
Rc::into_raw(cloned)
};
let objc = unsafe {
let alloc: id = msg_send![register_window_class_with_delegate::<T>(), alloc];
// Other types of backing (Retained/NonRetained) are archaic, dating back to the
// NeXTSTEP era, and are outright deprecated... so we don't allow setting them.
let buffered: NSUInteger = 2;
let dimensions: CGRect = config.initial_dimensions.into();
let window: id = msg_send![alloc, initWithContentRect:dimensions
styleMask:config.style
backing:buffered
defer:match config.defer {
true => YES,
false => NO
}
];
(&mut *window).set_ivar(WINDOW_DELEGATE_PTR, internal_callback_ptr as usize);
let _: () = msg_send![window, autorelease];
// This is very important! NSWindow is an old class and has some behavior that we need
// to disable, like... this. If we don't set this, we'll segfault entirely because the
// Objective-C runtime gets out of sync by releasing the window out from underneath of
// us.
let _: () = msg_send![window, setReleasedWhenClosed:NO];
// We set the window to be its own delegate - this is cleaned up inside `Drop`.
let _: () = msg_send![window, setDelegate:window];
ShareId::from_ptr(window)
};
{
let mut window_delegate = delegate.borrow_mut();
(*window_delegate).did_load(Window {
internal_callback_ptr: None,
delegate: None,
objc: objc.clone()
});
}
Window {
internal_callback_ptr: Some(internal_callback_ptr),
objc: objc,
delegate: Some(delegate)
}
}
}
/*impl<T> Drop for Window<T> {
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
/// safer than sorry.
///
/// We also clean up our loopback pointer that we use for callbacks.
fn drop(&mut self) {
unsafe {
if let Some(objc_controller) = &self.objc_controller.0 {
let window: id = msg_send![*objc_controller, window];
let _: () = msg_send![window, setDelegate:nil];
}
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}*/

View file

@ -2,22 +2,17 @@
//! module. There's a few different ones, and it's just... cleaner, if
//! it's organized here.
use crate::window::handle::WindowHandle;
use crate::window::WindowConfig;
use crate::window::Window;
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa
/// lifecycle methods, but mix in a few extra things to handle offering configuration tools
/// in lieu of subclasses.
pub trait WindowController {
/// The framework offers a standard, modern `NSWindow` by default - but sometimes you want
/// something else. Implement this and return your desired Window configuration.
fn config(&self) -> WindowConfig { WindowConfig::default() }
pub trait WindowDelegate {
/// Fires when this window has loaded in memory, and is about to display. This is a good point
/// to set up your views and what not.
///
/// If you're coming from the web, you can think of this as `DOMContentLoaded`.
fn did_load(&mut self, _window: WindowHandle) {}
fn did_load(&mut self, _window: Window) {}
/// Fires when a window is going to close. You might opt to, say, clean up things here -
/// perhaps you have a long running task, or something that should be removed.

View file

@ -1,20 +0,0 @@
[package]
name = "appkit-derive"
description = "A crate containing macros for appkit."
version = "0.1.0"
authors = ["Ryan McGrath <ryan@rymc.io>"]
edition = "2018"
license = "MPL-2.0+"
repository = "https://github.com/ryanmcgrath/appkit-rs"
categories = ["gui", "rendering::engine", "multimedia"]
keywords = ["gui", "ui"]
[lib]
proc-macro = true
[badges]
maintenance = { status = "actively-developed" }
[dependencies]
syn = "1.0.14"
quote = "1.0.2"

View file

@ -1,5 +0,0 @@
# Alchemy-Macros
This crate holds macros for `ShinkWrap`-esque wrappers for UI controlers, which enable a nicer programming experience - e.g, if you have a `ViewController`, and you want to (a level up in the stack) set a background color, it'd be as easy as calling `vc.set_background_color(...)`.
## Questions, Comments?
Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).

View file

@ -1,53 +0,0 @@
//! Macros used for `appkit-rs`. Mostly acting as `ShinkWrap`-esque forwarders.
//! Note that most of this is experimental!
extern crate proc_macro;
use crate::proc_macro::TokenStream;
use quote::quote;
use syn::{DeriveInput, parse_macro_input};
/// Derivces an `appkit::prelude::WindowWrapper` block, which implements forwarding methods for things
/// like setting the window title, or showing and closing it. It currently expects that the wrapped
/// struct has `window` as the field holding the `Window` from `appkit-rs`.
///
/// Note that this expects that pointers to Window(s) should not move once created.
#[proc_macro_derive(WindowWrapper)]
pub fn impl_window_wrapper(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let expanded = quote! {
impl #impl_generics appkit::prelude::WinWrapper for #name #ty_generics #where_clause {
fn set_title(&self, title: &str) { self.window.set_title(title); }
fn show(&self) { self.window.show(self); }
fn close(&self) { self.window.close(); }
}
};
TokenStream::from(expanded)
}
/// Derives an `appkit::prelude::ViewWrapper` block, which implements some necessary bits and
/// pieces for View handling.
#[proc_macro_derive(ViewWrapper)]
pub fn impl_view_wrapper(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let generics = input.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let expanded = quote! {
impl #impl_generics appkit::prelude::ViewWrapper for #name #ty_generics #where_clause {
fn get_handle(&self) -> Option<appkit::ShareId<appkit::Object>> {
self.view.get_handle()
}
}
};
TokenStream::from(expanded)
}

View file

@ -0,0 +1,10 @@
[package]
name = "window-controller"
version = "0.1.0"
authors = ["Ryan McGrath <ryan@rymc.io>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
appkit = { path = "../../appkit" }

View file

@ -0,0 +1,38 @@
//! This example showcases setting up a basic application and window controller.
//! A Window Controller is backed by `NSWindowController`, and typically used in scenarios where
//! you might have documents (backed by `NSDocument`) that you're working with.
//!
//! If you're not using that, you can probably get by fine with a standard `NSWindow`.
use appkit::app::{App, AppDelegate, Dispatcher};
use appkit::window::{Window, WindowConfig, WindowController, WindowDelegate};
struct BasicApp {
window: WindowController<MyWindow>
}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
self.window.show();
}
}
#[derive(Default)]
struct MyWindow;
impl WindowDelegate for MyWindow {
fn did_load(&mut self, window: Window) {
window.set_minimum_content_size(400., 400.);
window.set_title("A Basic Window!?");
}
fn will_close(&self) {
println!("Closing now!");
}
}
fn main() {
App::new("com.test.window-delegate", BasicApp {
window: WindowController::with(WindowConfig::default(), MyWindow::default())
}).run();
}

View file

@ -0,0 +1,10 @@
[package]
name = "window-delegate"
version = "0.1.0"
authors = ["Ryan McGrath <ryan@rymc.io>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
appkit = { path = "../../appkit" }

View file

@ -0,0 +1,35 @@
//! This example showcases setting up a basic application and window delegate.
//! Window Delegate's give you lifecycle methods that you can respond to.
use appkit::app::{App, AppDelegate, Dispatcher};
use appkit::window::{Window, WindowConfig, WindowDelegate};
struct BasicApp {
window: Window<MyWindow>
}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
self.window.show();
}
}
#[derive(Default)]
struct MyWindow;
impl WindowDelegate for MyWindow {
fn did_load(&mut self, window: Window) {
window.set_minimum_content_size(400., 400.);
window.set_title("A Basic Window!?");
}
fn will_close(&self) {
println!("Closing now!");
}
}
fn main() {
App::new("com.test.window-delegate", BasicApp {
window: Window::with(WindowConfig::default(), MyWindow::default())
}).run();
}

View file

@ -0,0 +1,8 @@
[package]
name = "window"
version = "0.1.0"
authors = ["Ryan McGrath <ryan@rymc.io>"]
edition = "2018"
[dependencies]
appkit = { path = "../../appkit" }

View file

@ -0,0 +1,21 @@
//! This example showcases setting up a basic application and window.
use appkit::app::{App, AppDelegate};
use appkit::window::Window;
#[derive(Default)]
struct BasicApp {
window: Window
}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
self.window.set_minimum_content_size(400., 400.);
self.window.set_title("A Basic Window");
self.window.show();
}
}
fn main() {
App::new("com.test.window", BasicApp::default()).run();
}