Moving to a model where ther are actual examples, since the handler logic is finally ironed out well.
This commit is contained in:
parent
bd82b8f27b
commit
f45c86743b
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,2 @@
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
placeholder
|
|
||||||
placeholder2
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ authors = ["Ryan McGrath <ryan@rymc.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
block = "0.1.6"
|
block = "0.1.6"
|
||||||
dispatch = "0.2.0"
|
dispatch = "0.2.0"
|
||||||
|
@ -18,4 +21,4 @@ url = "2.1.1"
|
||||||
cloudkit = []
|
cloudkit = []
|
||||||
user-notifications = ["uuid"]
|
user-notifications = ["uuid"]
|
||||||
webview = []
|
webview = []
|
||||||
enable-webview-downloading = []
|
webview-downloading = []
|
||||||
|
|
|
@ -15,7 +15,7 @@ use objc::runtime::{Class, Object, Sel};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
|
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::constants::APP_PTR;
|
||||||
use crate::error::AppKitError;
|
use crate::error::AppKitError;
|
||||||
use crate::printing::PrintSettings;
|
use crate::printing::PrintSettings;
|
||||||
|
@ -24,9 +24,9 @@ use crate::user_activity::UserActivity;
|
||||||
#[cfg(feature = "cloudkit")]
|
#[cfg(feature = "cloudkit")]
|
||||||
use crate::cloudkit::share::CKShareMetaData;
|
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.
|
/// 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 {
|
unsafe {
|
||||||
let app_ptr: usize = *this.get_ivar(APP_PTR);
|
let app_ptr: usize = *this.get_ivar(APP_PTR);
|
||||||
let app = app_ptr as *const T;
|
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.
|
/// 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();
|
app::<T>(this).will_finish_launching();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
/// 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();
|
app::<T>(this).did_finish_launching();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification.
|
/// 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();
|
app::<T>(this).will_become_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification.
|
/// 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();
|
app::<T>(this).did_become_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationWillResignActive` notification.
|
/// 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();
|
app::<T>(this).will_resign_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidResignActive` notification.
|
/// 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();
|
app::<T>(this).did_resign_active();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a 'applicationShouldTerminate:` notification.
|
/// 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()
|
app::<T>(this).should_terminate().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationWillTerminate:` notification.
|
/// 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();
|
app::<T>(this).will_terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationWillHide:` notification.
|
/// 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();
|
app::<T>(this).will_hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidHide:` notification.
|
/// 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();
|
app::<T>(this).did_hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationWillUnhide:` notification.
|
/// 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();
|
app::<T>(this).will_unhide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidUnhide:` notification.
|
/// 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();
|
app::<T>(this).did_unhide();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationWillUpdate:` notification.
|
/// 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();
|
app::<T>(this).will_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a `applicationDidUpdate:` notification.
|
/// 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();
|
app::<T>(this).did_update();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the Application Delegate receives a
|
/// Fires when the Application Delegate receives a
|
||||||
/// `applicationShouldHandleReopen:hasVisibleWindows:` notification.
|
/// `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 {
|
match app::<T>(this).should_handle_reopen(match has_visible_windows {
|
||||||
YES => true,
|
YES => true,
|
||||||
NO => false,
|
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.
|
/// 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() {
|
match app::<T>(this).dock_menu() {
|
||||||
Some(mut menu) => &mut *menu.inner,
|
Some(mut menu) => &mut *menu.inner,
|
||||||
None => nil
|
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.
|
/// 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);
|
let error = AppKitError::new(error);
|
||||||
app::<T>(this).will_present_error(error).into_nserror()
|
app::<T>(this).will_present_error(error).into_nserror()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification.
|
/// 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();
|
app::<T>(this).did_change_screen_parameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
|
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
|
||||||
/// notification.
|
/// 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);
|
let activity = NSString::wrap(activity_type);
|
||||||
|
|
||||||
match app::<T>(this).will_continue_user_activity(activity.to_str()) {
|
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.
|
/// 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
|
// @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
|
// much `NSObject` wrapping we want to do here. For now, pass the handler for whenever it's
|
||||||
// useful.
|
// useful.
|
||||||
|
@ -165,7 +165,7 @@ extern fn continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id,
|
||||||
|
|
||||||
/// Fires when the application receives a
|
/// Fires when the application receives a
|
||||||
/// `application:didFailToContinueUserActivityWithType:error:` message.
|
/// `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(
|
app::<T>(this).failed_to_continue_user_activity(
|
||||||
NSString::wrap(activity_type).to_str(),
|
NSString::wrap(activity_type).to_str(),
|
||||||
AppKitError::new(error)
|
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.
|
/// 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);
|
let activity = UserActivity::with_inner(activity);
|
||||||
app::<T>(this).updated_user_activity(activity);
|
app::<T>(this).updated_user_activity(activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message.
|
/// 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.
|
/// 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));
|
app::<T>(this).failed_to_register_for_remote_notifications(AppKitError::new(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives a `application:didReceiveRemoteNotification:` message.
|
/// 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:`
|
/// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:`
|
||||||
/// message.
|
/// message.
|
||||||
#[cfg(feature = "cloudkit")]
|
#[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);
|
let share = CKShareMetaData::with_inner(metadata);
|
||||||
app::<T>(this).user_accepted_cloudkit_share(share);
|
app::<T>(this).user_accepted_cloudkit_share(share);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fires when the application receives an `application:openURLs` message.
|
/// 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 urls = NSArray::wrap(file_urls).map(|url| {
|
||||||
let uri = NSString::wrap(unsafe {
|
let uri = NSString::wrap(unsafe {
|
||||||
msg_send![url, absoluteString]
|
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.
|
/// 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);
|
let filename = NSString::wrap(file);
|
||||||
|
|
||||||
match app::<T>(this).open_file_without_ui(filename.to_str()) {
|
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.
|
/// 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() {
|
match app::<T>(this).should_open_untitled_file() {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
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.
|
/// 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() {
|
match app::<T>(this).open_untitled_file() {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
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.
|
/// 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);
|
let filename = NSString::wrap(filename);
|
||||||
|
|
||||||
match app::<T>(this).open_temp_file(filename.to_str()) {
|
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.
|
/// 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);
|
let filename = NSString::wrap(file);
|
||||||
|
|
||||||
match app::<T>(this).print_file(filename.to_str()) {
|
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:`
|
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
|
||||||
/// message.
|
/// 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| {
|
let files = NSArray::wrap(files).map(|file| {
|
||||||
NSString::wrap(file).to_str().to_string()
|
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.
|
/// 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();
|
app::<T>(this).occlusion_state_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called when the application receives an `application:delegateHandlesKey:` message.
|
/// Called when the application receives an `application:delegateHandlesKey:` message.
|
||||||
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
|
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
|
||||||
/// matter.
|
/// 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);
|
let key = NSString::wrap(key);
|
||||||
|
|
||||||
match app::<T>(this).delegate_handles_key(key.to_str()) {
|
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
|
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
|
||||||
/// pointers we need to have.
|
/// pointers we need to have.
|
||||||
pub(crate) fn register_app_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 mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
INIT.call_once(|| unsafe {
|
INIT.call_once(|| unsafe {
|
||||||
let superclass = class!(NSObject);
|
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);
|
decl.add_ivar::<usize>(APP_PTR);
|
||||||
|
|
|
@ -12,13 +12,13 @@ use crate::menu::Menu;
|
||||||
mod class;
|
mod class;
|
||||||
use class::register_app_class;
|
use class::register_app_class;
|
||||||
|
|
||||||
mod controller;
|
mod delegate;
|
||||||
use controller::register_app_controller_class;
|
use delegate::register_app_delegate_class;
|
||||||
|
|
||||||
pub mod enums;
|
pub mod enums;
|
||||||
|
|
||||||
pub mod traits;
|
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,
|
/// 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,
|
/// 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
|
/// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation
|
||||||
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
||||||
/// Objective-C side of things.
|
/// 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 app_delegate = Box::new(delegate);
|
||||||
|
|
||||||
let objc_delegate = unsafe {
|
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: id = msg_send![delegate_class, new];
|
||||||
let delegate_ptr: *const T = &*app_delegate;
|
let delegate_ptr: *const T = &*app_delegate;
|
||||||
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
|
(&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,
|
/// 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.
|
/// and passing back through there. All messages are currently dispatched on the main thread.
|
||||||
pub fn dispatch(message: M) {
|
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);
|
(&*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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -21,11 +21,11 @@ pub trait Dispatcher {
|
||||||
fn on_message(&self, _message: Self::Message) {}
|
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
|
/// 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
|
/// implement methods from this trait in order to respond to lifecycle events that the system will
|
||||||
/// fire off.
|
/// fire off.
|
||||||
pub trait AppController {
|
pub trait AppDelegate {
|
||||||
/// Called right before the application will finish launching. You really, probably, want to do
|
/// 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.
|
/// your setup in `did_finish_launching` unless you're sure of what you're doing.
|
||||||
fn will_finish_launching(&self) {}
|
fn will_finish_launching(&self) {}
|
|
@ -6,6 +6,7 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
if std::env::var("TARGET").unwrap().contains("-apple") {
|
if std::env::var("TARGET").unwrap().contains("-apple") {
|
||||||
println!("cargo:rustc-link-lib=framework=Foundation");
|
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=CoreGraphics");
|
||||||
|
|
||||||
println!("cargo:rustc-link-lib=framework=Security");
|
println!("cargo:rustc-link-lib=framework=Security");
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
pub(crate) static APP_PTR: &str = "rstAppPtr";
|
pub(crate) static APP_PTR: &str = "rstAppPtr";
|
||||||
pub(crate) static BACKGROUND_COLOR: &str = "rstBackgroundColor";
|
pub(crate) static BACKGROUND_COLOR: &str = "rstBackgroundColor";
|
||||||
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
|
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")]
|
#[cfg(feature = "webview")]
|
||||||
pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig";
|
pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig";
|
||||||
|
@ -14,5 +14,3 @@ pub(crate) static WEBVIEW_VAR: &str = "rstWebView";
|
||||||
|
|
||||||
#[cfg(feature = "webview")]
|
#[cfg(feature = "webview")]
|
||||||
pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";
|
pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";
|
||||||
|
|
||||||
pub(crate) static WINDOW_CONTROLLER_PTR: &str = "rstWindowController";
|
|
|
@ -3,6 +3,7 @@
|
||||||
use crate::foundation::{CGRect, CGPoint, CGSize};
|
use crate::foundation::{CGRect, CGPoint, CGSize};
|
||||||
|
|
||||||
/// A struct that represents a box - top, left, width and height.
|
/// A struct that represents a box - top, left, width and height.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct Rect {
|
pub struct Rect {
|
||||||
/// Distance from the top, in points.
|
/// Distance from the top, in points.
|
||||||
pub top: f64,
|
pub top: f64,
|
|
@ -9,7 +9,7 @@ pub trait Layout {
|
||||||
/// Returns a reference to the backing Objective-C layer. This is optional, as we try to keep
|
/// 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
|
/// 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).
|
/// 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.
|
/// This trait should implement adding a view to the subview tree for a given view.
|
||||||
fn add_subview<V: Layout>(&self, _view: &V);
|
fn add_subview<V: Layout>(&self, _view: &V);
|
|
@ -44,7 +44,7 @@ pub mod printing;
|
||||||
pub mod toolbar;
|
pub mod toolbar;
|
||||||
pub mod user_activity;
|
pub mod user_activity;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
pub mod view;
|
//pub mod view;
|
||||||
|
|
||||||
#[cfg(feature = "webview")]
|
#[cfg(feature = "webview")]
|
||||||
pub mod webview;
|
pub mod webview;
|
||||||
|
@ -55,7 +55,7 @@ pub mod window;
|
||||||
pub use url;
|
pub use url;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::app::{App, AppController, Dispatcher};
|
pub use crate::app::{App, AppDelegate, Dispatcher};
|
||||||
|
|
||||||
pub use crate::layout::LayoutConstraint;
|
pub use crate::layout::LayoutConstraint;
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ pub mod prelude {
|
||||||
pub use crate::networking::URLRequest;
|
pub use crate::networking::URLRequest;
|
||||||
|
|
||||||
pub use crate::window::{
|
pub use crate::window::{
|
||||||
Window, WindowController, WindowHandle
|
Window, /*WindowController,*/ WindowDelegate
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "webview")]
|
#[cfg(feature = "webview")]
|
||||||
|
@ -77,5 +77,5 @@ pub mod prelude {
|
||||||
WebView, WebViewConfig, WebViewController
|
WebView, WebViewConfig, WebViewController
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::view::{View, ViewHandle, ViewController};
|
//pub use crate::view::{View, ViewController, ViewDelegate};
|
||||||
}
|
}
|
|
@ -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 }
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,13 +12,13 @@ use std::sync::Once;
|
||||||
|
|
||||||
use objc::declare::ClassDecl;
|
use objc::declare::ClassDecl;
|
||||||
use objc::runtime::{Class, Object, Sel, BOOL};
|
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSUInteger};
|
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::dragdrop::DragInfo;
|
||||||
use crate::view::traits::ViewController;
|
use crate::view::traits::ViewDelegate;
|
||||||
use crate::utils::load;
|
use crate::utils::load;
|
||||||
|
|
||||||
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
|
||||||
|
@ -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).
|
/// 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) {
|
extern fn update_layer(this: &Object, _: Sel) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
|
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.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
|
extern fn dragging_entered<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
|
||||||
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
|
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
let response = {
|
let response = {
|
||||||
let v = view.borrow();
|
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.
|
/// 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 {
|
extern fn prepare_for_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||||
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
|
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
let response = {
|
let response = {
|
||||||
let v = view.borrow();
|
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.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
extern fn perform_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||||
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
|
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
let response = {
|
let response = {
|
||||||
let v = view.borrow();
|
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.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn conclude_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) {
|
extern fn conclude_drag_operation<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) {
|
||||||
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
|
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
{
|
{
|
||||||
let v = view.borrow();
|
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.
|
/// Called when a drag/drop operation has entered this view.
|
||||||
extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, info: id) {
|
extern fn dragging_exited<T: ViewDelegate>(this: &mut Object, _: Sel, info: id) {
|
||||||
let view = load::<T>(this, VIEW_CONTROLLER_PTR);
|
let view = load::<T>(this, VIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
{
|
{
|
||||||
let v = view.borrow();
|
let v = view.borrow();
|
||||||
|
@ -120,19 +121,41 @@ extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, info: id
|
||||||
Rc::into_raw(view);
|
Rc::into_raw(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Injects an `NSView` subclass, with some callback and pointer ivars for what we
|
/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we
|
||||||
/// need to do.
|
/// have separate classes here since we don't want to waste cycles on methods that will never be
|
||||||
pub(crate) fn register_view_class<T: ViewController>() -> *const Class {
|
/// 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 mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||||
static INIT: Once = Once::new();
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
INIT.call_once(|| unsafe {
|
INIT.call_once(|| unsafe {
|
||||||
let superclass = Class::get("NSView").unwrap();
|
let superclass = class!(NSView);
|
||||||
let mut decl = ClassDecl::new("RSTView", superclass).unwrap();
|
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
|
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
|
||||||
// move.
|
// move.
|
||||||
decl.add_ivar::<usize>(VIEW_CONTROLLER_PTR);
|
decl.add_ivar::<usize>(VIEW_DELEGATE_PTR);
|
||||||
decl.add_ivar::<id>(BACKGROUND_COLOR);
|
decl.add_ivar::<id>(BACKGROUND_COLOR);
|
||||||
|
|
||||||
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
83
appkit/view/controller/class.rs
Normal file
83
appkit/view/controller/class.rs
Normal 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 }
|
||||||
|
}
|
39
appkit/view/controller/mod.rs
Normal file
39
appkit/view/controller/mod.rs
Normal 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
207
appkit/view/mod.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,25 @@
|
||||||
//! Various traits used for Views.
|
//! Various traits used for Views.
|
||||||
|
|
||||||
use crate::dragdrop::{DragInfo, DragOperation};
|
use crate::dragdrop::{DragInfo, DragOperation};
|
||||||
use crate::view::ViewHandle;
|
use crate::view::View;
|
||||||
|
|
||||||
pub trait ViewController {
|
pub trait ViewDelegate {
|
||||||
/// Where possible, we try to respect the lazy-ish-loading of macOS/iOS systems. This hook
|
/// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to
|
||||||
/// notifies you when the `View` has actually loaded into memory, and you're free to do things
|
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
|
||||||
/// with it.
|
/// main thread!
|
||||||
///
|
fn did_load(&mut self, _view: View) {}
|
||||||
/// 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. :)
|
/// Called when this is about to be added to the view heirarchy.
|
||||||
fn did_load(&mut self, _view: ViewHandle) {}
|
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.
|
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
|
||||||
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }
|
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }
|
|
@ -1,36 +1,11 @@
|
||||||
//! Implements wrappers around `WKNavigationAction` and `WKNavigationActionPolicy`.
|
//! 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 objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::{id, BOOL, YES, NO, NSInteger};
|
||||||
use crate::networking::URLRequest;
|
use crate::networking::URLRequest;
|
||||||
|
|
||||||
pub enum NavigationType {
|
#[derive(Debug)]
|
||||||
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); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NavigationAction {
|
pub struct NavigationAction {
|
||||||
pub navigation_type: NavigationType,
|
pub navigation_type: NavigationType,
|
||||||
pub request: URLRequest
|
pub request: URLRequest
|
||||||
|
@ -51,20 +26,7 @@ impl NavigationAction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum NavigationPolicy {
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
Cancel,
|
|
||||||
Allow
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<NSInteger> for NavigationPolicy {
|
|
||||||
fn into(self) -> NSInteger {
|
|
||||||
match self {
|
|
||||||
NavigationPolicy::Cancel => 0,
|
|
||||||
NavigationPolicy::Allow => 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NavigationResponse {
|
pub struct NavigationResponse {
|
||||||
pub can_show_mime_type: bool
|
pub can_show_mime_type: bool
|
||||||
}
|
}
|
||||||
|
@ -80,25 +42,8 @@ impl NavigationResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum NavigationResponsePolicy {
|
|
||||||
Cancel = 0,
|
|
||||||
Allow = 1,
|
|
||||||
|
|
||||||
// This is a private API!
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
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)]
|
|
||||||
pub struct OpenPanelParameters {
|
pub struct OpenPanelParameters {
|
||||||
pub allows_directories: bool,
|
pub allows_directories: bool,
|
||||||
pub allows_multiple_selection: bool
|
pub allows_multiple_selection: bool
|
66
appkit/webview/config.rs
Normal file
66
appkit/webview/config.rs
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,18 +7,16 @@ use std::ffi::c_void;
|
||||||
|
|
||||||
use block::Block;
|
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::declare::ClassDecl;
|
||||||
use objc::runtime::{Class, Object, Sel};
|
use objc::runtime::{Class, Object, Sel};
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
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::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
|
||||||
|
use crate::geometry::Rect;
|
||||||
use crate::view::traits::ViewController;
|
use crate::view::traits::ViewController;
|
||||||
use crate::webview::action::{NavigationAction, NavigationResponse};
|
use crate::webview::action::{NavigationAction, NavigationResponse};
|
||||||
use crate::webview::traits::WebViewController;
|
use crate::webview::traits::WebViewController;
|
||||||
use crate::utils::str_from;
|
|
||||||
|
|
||||||
/// Loads and configures ye old WKWebView/View for this controller.
|
/// Loads and configures ye old WKWebView/View for this controller.
|
||||||
extern fn load_view<T: ViewController + WebViewController>(this: &mut Object, _: Sel) {
|
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);
|
let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR);
|
||||||
|
|
||||||
// Technically private!
|
// Technically private!
|
||||||
#[cfg(feature = "enable-webview-downloading")]
|
#[cfg(feature = "webview-downloading")]
|
||||||
let process_pool: id = msg_send![configuration, processPool];
|
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 _: () = 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_alloc: id = msg_send![class!(WKWebView), alloc];
|
||||||
let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration];
|
let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration];
|
||||||
let _: () = msg_send![webview, setWantsLayer:YES];
|
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
|
/// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your
|
||||||
/// `WebViewController`, where you should handle the event.
|
/// `WebViewController`, where you should handle the event.
|
||||||
extern fn alert<T: WebViewController + 'static>(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) {
|
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);
|
println!("Alert: {}", alert);
|
||||||
|
|
||||||
// @TODO: This is technically (I think?) a private method, and there's some other dance that
|
// @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`.
|
/// 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) {
|
extern fn on_message<T: WebViewController + 'static>(this: &Object, _: Sel, _: id, script_message: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = str_from(msg_send![script_message, name]);
|
let name = NSString::wrap(msg_send![script_message, name]).to_str();
|
||||||
let body = str_from(msg_send![script_message, body]);
|
let body = NSString::wrap(msg_send![script_message, body]).to_str();
|
||||||
|
|
||||||
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR);
|
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR);
|
||||||
let webview = ptr as *const T;
|
let webview = ptr as *const T;
|
||||||
|
@ -140,13 +138,12 @@ extern fn run_open_panel<T: WebViewController + 'static>(this: &Object, _: Sel,
|
||||||
|
|
||||||
match urls {
|
match urls {
|
||||||
Some(u) => {
|
Some(u) => {
|
||||||
let nsurls: Vec<id> = u.iter().map(|s| {
|
let nsurls: NSArray = u.iter().map(|s| {
|
||||||
let s = NSString::alloc(nil).init_str(&s);
|
let s = NSString::new(&s);
|
||||||
msg_send![class!(NSURL), URLWithString:s]
|
msg_send![class!(NSURL), URLWithString:s]
|
||||||
}).collect();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
let array = NSArray::arrayWithObjects(nil, &nsurls);
|
(*handler).call((nsurls.into_inner(),));
|
||||||
(*handler).call((array,));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
None => { (*handler).call((nil,)); }
|
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
|
/// 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
|
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
|
||||||
/// API.
|
/// 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) {
|
extern fn handle_download<T: WebViewController + 'static>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) {
|
||||||
let webview = unsafe {
|
let webview = unsafe {
|
||||||
let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR);
|
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 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 {
|
webview.run_save_panel(filename, move |can_overwrite, path| unsafe {
|
||||||
if path.is_none() {
|
if path.is_none() {
|
||||||
let _: () = msg_send![download, cancel];
|
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 {
|
(*handler).call((match can_overwrite {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
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.
|
// 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
|
// 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.
|
// 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));
|
decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize));
|
||||||
|
|
||||||
VIEW_CLASS = decl.register();
|
VIEW_CLASS = decl.register();
|
100
appkit/webview/enums.rs
Normal file
100
appkit/webview/enums.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your
|
//! A `WebViewHandle` represents an underlying `WKWebView`. 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
|
//! `WebViewController::did_load()` method. This method is safe to store and use, however as it's
|
||||||
//! UI-specific it's not thread safe.
|
//! UI-specific it's not thread safe.
|
||||||
//!
|
//!
|
||||||
//! You can use this struct to configure how a view should look and layout. It implements
|
//! 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;
|
use crate::pasteboard::PasteboardType;
|
||||||
|
|
||||||
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
|
/// 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.
|
/// side anyway.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ViewHandle {
|
pub struct WebViewHandle {
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: Option<ShareId<Object>>,
|
pub objc: Option<ShareId<Object>>,
|
||||||
|
|
||||||
|
@ -48,13 +48,13 @@ pub struct ViewHandle {
|
||||||
pub center_y: LayoutAnchorY
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewHandle {
|
impl WebViewHandle {
|
||||||
pub(crate) fn new(object: ShareId<Object>) -> Self {
|
pub(crate) fn new(object: ShareId<Object>) -> Self {
|
||||||
let view: id = unsafe {
|
let view: id = unsafe {
|
||||||
msg_send![&*object, view]
|
msg_send![&*object, view]
|
||||||
};
|
};
|
||||||
|
|
||||||
ViewHandle {
|
WebViewHandle {
|
||||||
objc: Some(object),
|
objc: Some(object),
|
||||||
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
||||||
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
||||||
|
@ -66,46 +66,4 @@ impl ViewHandle {
|
||||||
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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
|
//! - `WKWebView`
|
||||||
//! lifecycle methods accordingly. This is a heap allocation.
|
//! - `WKUIDelegate`
|
||||||
//! - A `ViewController` trait, which enables you to hook into the `NSViewController` lifecycle
|
//! - `WKScriptMessageHandler`
|
||||||
//! methods.
|
//! - `NSViewController`
|
||||||
//! - 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.
|
|
||||||
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use objc_id::ShareId;
|
use objc_id::ShareId;
|
||||||
use objc::runtime::Object;
|
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::foundation::{id, nil, YES, NO, NSString};
|
||||||
use crate::color::Color;
|
use crate::constants::WEBVIEW_CONTROLLER_PTR;
|
||||||
use crate::constants::VIEW_CONTROLLER_PTR;
|
use crate::webview::controller::register_controller_class;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
|
||||||
use crate::pasteboard::PasteboardType;
|
|
||||||
|
|
||||||
mod class;
|
pub mod actions;
|
||||||
mod controller;
|
|
||||||
use controller::register_controller_class;
|
pub(crate) mod controller;
|
||||||
|
//pub(crate) mod process_pool;
|
||||||
|
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
pub use traits::ViewController;
|
pub use traits::WebViewController;
|
||||||
|
|
||||||
pub mod handle;
|
pub mod config;
|
||||||
pub use handle::ViewHandle;
|
pub use config::WebViewConfig;
|
||||||
|
|
||||||
/// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards
|
/// 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
|
/// calls into your supplied `ViewController` trait object. This involves heap allocation, but all
|
||||||
/// of Cocoa is essentially Heap'd, so... well, enjoy.
|
/// of Cocoa is essentially Heap'd, so... well, enjoy.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct View<T> {
|
pub struct WebView<T> {
|
||||||
internal_callback_ptr: *const RefCell<T>,
|
internal_callback_ptr: *const RefCell<T>,
|
||||||
pub objc_controller: ViewHandle,
|
pub objc_controller: WebViewHandle,
|
||||||
pub controller: Rc<RefCell<T>>
|
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
|
/// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over
|
||||||
/// to your supplied view controller.
|
/// to your supplied view controller.
|
||||||
pub fn new(controller: T) -> Self {
|
pub fn new(controller: T) -> Self {
|
||||||
|
let config = controller.config();
|
||||||
let controller = Rc::new(RefCell::new(controller));
|
let controller = Rc::new(RefCell::new(controller));
|
||||||
|
|
||||||
let internal_callback_ptr = {
|
let internal_callback_ptr = {
|
||||||
|
@ -72,24 +50,26 @@ impl<T> View<T> where T: ViewController + 'static {
|
||||||
Rc::into_raw(cloned)
|
Rc::into_raw(cloned)
|
||||||
};
|
};
|
||||||
|
|
||||||
let inner = unsafe {
|
let handle = WebViewHandle::new(unsafe {
|
||||||
let view_controller: id = msg_send![register_controller_class::<T>(), new];
|
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];
|
// WKWebView isn't really great to subclass, so we don't bother here unlike other
|
||||||
(&mut *view).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
|
// 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)
|
ShareId::from_ptr(view_controller)
|
||||||
};
|
});
|
||||||
|
|
||||||
let handle = ViewHandle::new(inner);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut vc = controller.borrow_mut();
|
let mut vc = controller.borrow_mut();
|
||||||
(*vc).did_load(handle.clone());
|
(*vc).did_load(handle.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
View {
|
WebView {
|
||||||
internal_callback_ptr: internal_callback_ptr,
|
internal_callback_ptr: internal_callback_ptr,
|
||||||
objc_controller: handle,
|
objc_controller: handle,
|
||||||
controller: controller
|
controller: controller
|
|
@ -2,14 +2,40 @@
|
||||||
//! `WKWebView`. It allows you to do things such as handle opening a file (for uploads or
|
//! `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.
|
//! 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,
|
NavigationAction, NavigationPolicy,
|
||||||
NavigationResponse, NavigationResponsePolicy,
|
NavigationResponse, NavigationResponsePolicy,
|
||||||
OpenPanelParameters
|
OpenPanelParameters
|
||||||
};
|
};
|
||||||
|
use crate::webview::handle::WebViewHandle;
|
||||||
|
|
||||||
/// You can implement this on structs to handle callbacks from the underlying `WKWebView`.
|
/// You can implement this on structs to handle callbacks from the underlying `WKWebView`.
|
||||||
pub trait WebViewController {
|
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
|
/// 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
|
/// `notify` as a callback, and in the browser you called
|
||||||
/// `webkit.messageHandlers.notify.postMessage({...})` it would wind up here, with `name` being
|
/// `webkit.messageHandlers.notify.postMessage({...})` it would wind up here, with `name` being
|
||||||
|
@ -41,7 +67,7 @@ pub trait WebViewController {
|
||||||
/// and thread the callbacks accordingly.
|
/// and thread the callbacks accordingly.
|
||||||
///
|
///
|
||||||
/// Note that this specific callback is only
|
/// 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
|
/// 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
|
/// 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
|
/// downloading/saving. This is due to Apple not allowing the private methods on `WKWebView` to
|
68
appkit/window/class.rs
Normal file
68
appkit/window/class.rs
Normal 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
57
appkit/window/config.rs
Normal 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 it’s 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;
|
||||||
|
}
|
||||||
|
}
|
28
appkit/window/controller/class.rs
Normal file
28
appkit/window/controller/class.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
88
appkit/window/controller/mod.rs
Normal file
88
appkit/window/controller/mod.rs
Normal 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
98
appkit/window/enums.rs
Normal 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
251
appkit/window/mod.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
|
@ -2,22 +2,17 @@
|
||||||
//! module. There's a few different ones, and it's just... cleaner, if
|
//! module. There's a few different ones, and it's just... cleaner, if
|
||||||
//! it's organized here.
|
//! it's organized here.
|
||||||
|
|
||||||
use crate::window::handle::WindowHandle;
|
use crate::window::Window;
|
||||||
use crate::window::WindowConfig;
|
|
||||||
|
|
||||||
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa
|
/// 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
|
/// lifecycle methods, but mix in a few extra things to handle offering configuration tools
|
||||||
/// in lieu of subclasses.
|
/// in lieu of subclasses.
|
||||||
pub trait WindowController {
|
pub trait WindowDelegate {
|
||||||
/// 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() }
|
|
||||||
|
|
||||||
/// Fires when this window has loaded in memory, and is about to display. This is a good point
|
/// 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.
|
/// to set up your views and what not.
|
||||||
///
|
///
|
||||||
/// If you're coming from the web, you can think of this as `DOMContentLoaded`.
|
/// 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 -
|
/// 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.
|
/// perhaps you have a long running task, or something that should be removed.
|
|
@ -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"
|
|
|
@ -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/).
|
|
|
@ -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)
|
|
||||||
}
|
|
10
examples/window-controller/Cargo.toml
Normal file
10
examples/window-controller/Cargo.toml
Normal 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" }
|
38
examples/window-controller/src/main.rs
Normal file
38
examples/window-controller/src/main.rs
Normal 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();
|
||||||
|
}
|
10
examples/window-delegate/Cargo.toml
Normal file
10
examples/window-delegate/Cargo.toml
Normal 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" }
|
35
examples/window-delegate/src/main.rs
Normal file
35
examples/window-delegate/src/main.rs
Normal 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();
|
||||||
|
}
|
8
examples/window/Cargo.toml
Normal file
8
examples/window/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "window"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Ryan McGrath <ryan@rymc.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
appkit = { path = "../../appkit" }
|
21
examples/window/src/main.rs
Normal file
21
examples/window/src/main.rs
Normal 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();
|
||||||
|
}
|
Loading…
Reference in a new issue