From f45c86743bec35508454607015a698bc549119be Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Thu, 19 Mar 2020 20:07:44 -0700 Subject: [PATCH] Moving to a model where ther are actual examples, since the handler logic is finally ironed out well. --- .gitignore | 2 - appkit/Cargo.toml | 5 +- appkit/{src => }/alert.rs | 0 appkit/{src => }/app/class.rs | 0 .../app/controller.rs => app/delegate.rs} | 80 +++--- appkit/{src => }/app/enums.rs | 0 appkit/{src => }/app/mod.rs | 38 +-- appkit/{src => }/app/traits.rs | 4 +- appkit/build.rs | 1 + appkit/{src => }/bundle.rs | 0 appkit/{src => }/button.rs | 0 appkit/{src => }/cloudkit/mod.rs | 0 appkit/{src => }/cloudkit/share.rs | 0 appkit/{src => }/collection_view/mod.rs | 0 appkit/{src => }/collection_view/traits.rs | 0 appkit/{src => }/color.rs | 0 appkit/{src => }/constants.rs | 4 +- appkit/{src => }/dragdrop.rs | 0 appkit/{src => }/error.rs | 0 appkit/{src => }/events.rs | 0 appkit/{src => }/filesystem/enums.rs | 0 appkit/{src => }/filesystem/manager.rs | 0 appkit/{src => }/filesystem/mod.rs | 0 appkit/{src => }/filesystem/save.rs | 0 appkit/{src => }/filesystem/select.rs | 0 appkit/{src => }/filesystem/traits.rs | 0 appkit/{src => }/foundation/array.rs | 0 .../{src => }/foundation/autoreleasepool.rs | 0 appkit/{src => }/foundation/dictionary.rs | 0 appkit/{src => }/foundation/geometry.rs | 0 appkit/{src => }/foundation/mod.rs | 0 appkit/{src => }/foundation/string.rs | 0 appkit/{src => }/geometry.rs | 1 + appkit/{src => }/layout/constraint.rs | 0 appkit/{src => }/layout/dimension.rs | 0 appkit/{src => }/layout/horizontal.rs | 0 appkit/{src => }/layout/mod.rs | 0 appkit/{src => }/layout/traits.rs | 2 +- appkit/{src => }/layout/vertical.rs | 0 appkit/{src => }/lib.rs | 8 +- appkit/{src => }/menu/item.rs | 0 appkit/{src => }/menu/menu.rs | 0 appkit/{src => }/menu/mod.rs | 0 appkit/{src => }/networking/mod.rs | 0 appkit/{src => }/notifications/enums.rs | 0 appkit/{src => }/notifications/mod.rs | 0 .../{src => }/notifications/notifications.rs | 0 appkit/{src => }/pasteboard/mod.rs | 0 appkit/{src => }/pasteboard/types.rs | 0 appkit/{src => }/printing/enums.rs | 0 appkit/{src => }/printing/mod.rs | 0 appkit/{src => }/printing/settings.rs | 0 appkit/src/view/controller.rs | 45 ---- appkit/src/webview/config.rs | 44 --- appkit/src/webview/mod.rs | 15 -- appkit/src/webview/webview.rs | 126 --------- appkit/src/window/config.rs | 54 ---- appkit/src/window/controller.rs | 52 ---- appkit/src/window/enums.rs | 15 -- appkit/src/window/handle.rs | 134 ---------- appkit/src/window/mod.rs | 166 ------------ appkit/src/window/window.rs | 118 -------- appkit/{src => }/toolbar/class.rs | 0 appkit/{src => }/toolbar/handle.rs | 0 appkit/{src => }/toolbar/item.rs | 0 appkit/{src => }/toolbar/mod.rs | 0 appkit/{src => }/toolbar/toolbar.rs | 0 appkit/{src => }/toolbar/traits.rs | 0 appkit/{src => }/toolbar/types.rs | 0 appkit/{src => }/user_activity.rs | 0 appkit/{src => }/utils.rs | 0 appkit/{src => }/view/class.rs | 59 ++-- appkit/view/controller/class.rs | 83 ++++++ appkit/view/controller/mod.rs | 39 +++ appkit/view/mod.rs | 207 +++++++++++++++ appkit/{src => }/view/traits.rs | 27 +- .../webview/action.rs => webview/actions.rs} | 63 +---- appkit/webview/config.rs | 66 +++++ appkit/{src => }/webview/controller.rs | 37 ++- appkit/webview/enums.rs | 100 +++++++ appkit/{src/view => webview}/handle.rs | 54 +--- appkit/{src/view => webview}/mod.rs | 82 +++--- appkit/{src => }/webview/process_pool.rs | 0 appkit/{src => }/webview/traits.rs | 30 ++- appkit/window/class.rs | 68 +++++ appkit/window/config.rs | 57 ++++ appkit/window/controller/class.rs | 28 ++ appkit/window/controller/mod.rs | 88 ++++++ appkit/window/enums.rs | 98 +++++++ appkit/window/mod.rs | 251 ++++++++++++++++++ appkit/{src => }/window/traits.rs | 11 +- derives/Cargo.toml | 20 -- derives/README.md | 5 - derives/src/lib.rs | 53 ---- examples/window-controller/Cargo.toml | 10 + examples/window-controller/src/main.rs | 38 +++ examples/window-delegate/Cargo.toml | 10 + examples/window-delegate/src/main.rs | 35 +++ examples/window/Cargo.toml | 8 + examples/window/src/main.rs | 21 ++ 100 files changed, 1429 insertions(+), 1133 deletions(-) rename appkit/{src => }/alert.rs (100%) rename appkit/{src => }/app/class.rs (100%) rename appkit/{src/app/controller.rs => app/delegate.rs} (79%) rename appkit/{src => }/app/enums.rs (100%) rename appkit/{src => }/app/mod.rs (92%) rename appkit/{src => }/app/traits.rs (99%) rename appkit/{src => }/bundle.rs (100%) rename appkit/{src => }/button.rs (100%) rename appkit/{src => }/cloudkit/mod.rs (100%) rename appkit/{src => }/cloudkit/share.rs (100%) rename appkit/{src => }/collection_view/mod.rs (100%) rename appkit/{src => }/collection_view/traits.rs (100%) rename appkit/{src => }/color.rs (100%) rename appkit/{src => }/constants.rs (79%) rename appkit/{src => }/dragdrop.rs (100%) rename appkit/{src => }/error.rs (100%) rename appkit/{src => }/events.rs (100%) rename appkit/{src => }/filesystem/enums.rs (100%) rename appkit/{src => }/filesystem/manager.rs (100%) rename appkit/{src => }/filesystem/mod.rs (100%) rename appkit/{src => }/filesystem/save.rs (100%) rename appkit/{src => }/filesystem/select.rs (100%) rename appkit/{src => }/filesystem/traits.rs (100%) rename appkit/{src => }/foundation/array.rs (100%) rename appkit/{src => }/foundation/autoreleasepool.rs (100%) rename appkit/{src => }/foundation/dictionary.rs (100%) rename appkit/{src => }/foundation/geometry.rs (100%) rename appkit/{src => }/foundation/mod.rs (100%) rename appkit/{src => }/foundation/string.rs (100%) rename appkit/{src => }/geometry.rs (97%) rename appkit/{src => }/layout/constraint.rs (100%) rename appkit/{src => }/layout/dimension.rs (100%) rename appkit/{src => }/layout/horizontal.rs (100%) rename appkit/{src => }/layout/mod.rs (100%) rename appkit/{src => }/layout/traits.rs (91%) rename appkit/{src => }/layout/vertical.rs (100%) rename appkit/{src => }/lib.rs (92%) rename appkit/{src => }/menu/item.rs (100%) rename appkit/{src => }/menu/menu.rs (100%) rename appkit/{src => }/menu/mod.rs (100%) rename appkit/{src => }/networking/mod.rs (100%) rename appkit/{src => }/notifications/enums.rs (100%) rename appkit/{src => }/notifications/mod.rs (100%) rename appkit/{src => }/notifications/notifications.rs (100%) rename appkit/{src => }/pasteboard/mod.rs (100%) rename appkit/{src => }/pasteboard/types.rs (100%) rename appkit/{src => }/printing/enums.rs (100%) rename appkit/{src => }/printing/mod.rs (100%) rename appkit/{src => }/printing/settings.rs (100%) delete mode 100644 appkit/src/view/controller.rs delete mode 100644 appkit/src/webview/config.rs delete mode 100644 appkit/src/webview/mod.rs delete mode 100644 appkit/src/webview/webview.rs delete mode 100644 appkit/src/window/config.rs delete mode 100644 appkit/src/window/controller.rs delete mode 100644 appkit/src/window/enums.rs delete mode 100644 appkit/src/window/handle.rs delete mode 100644 appkit/src/window/mod.rs delete mode 100644 appkit/src/window/window.rs rename appkit/{src => }/toolbar/class.rs (100%) rename appkit/{src => }/toolbar/handle.rs (100%) rename appkit/{src => }/toolbar/item.rs (100%) rename appkit/{src => }/toolbar/mod.rs (100%) rename appkit/{src => }/toolbar/toolbar.rs (100%) rename appkit/{src => }/toolbar/traits.rs (100%) rename appkit/{src => }/toolbar/types.rs (100%) rename appkit/{src => }/user_activity.rs (100%) rename appkit/{src => }/utils.rs (100%) rename appkit/{src => }/view/class.rs (67%) create mode 100644 appkit/view/controller/class.rs create mode 100644 appkit/view/controller/mod.rs create mode 100644 appkit/view/mod.rs rename appkit/{src => }/view/traits.rs (60%) rename appkit/{src/webview/action.rs => webview/actions.rs} (57%) create mode 100644 appkit/webview/config.rs rename appkit/{src => }/webview/controller.rs (89%) create mode 100644 appkit/webview/enums.rs rename appkit/{src/view => webview}/handle.rs (54%) rename appkit/{src/view => webview}/mod.rs (59%) rename appkit/{src => }/webview/process_pool.rs (100%) rename appkit/{src => }/webview/traits.rs (68%) create mode 100644 appkit/window/class.rs create mode 100644 appkit/window/config.rs create mode 100644 appkit/window/controller/class.rs create mode 100644 appkit/window/controller/mod.rs create mode 100644 appkit/window/enums.rs create mode 100644 appkit/window/mod.rs rename appkit/{src => }/window/traits.rs (85%) delete mode 100644 derives/Cargo.toml delete mode 100644 derives/README.md delete mode 100644 derives/src/lib.rs create mode 100644 examples/window-controller/Cargo.toml create mode 100644 examples/window-controller/src/main.rs create mode 100644 examples/window-delegate/Cargo.toml create mode 100644 examples/window-delegate/src/main.rs create mode 100644 examples/window/Cargo.toml create mode 100644 examples/window/src/main.rs diff --git a/.gitignore b/.gitignore index 7ea5e83..a9d37c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ target Cargo.lock -placeholder -placeholder2 diff --git a/appkit/Cargo.toml b/appkit/Cargo.toml index 233326d..6781180 100644 --- a/appkit/Cargo.toml +++ b/appkit/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Ryan McGrath "] edition = "2018" build = "build.rs" +[lib] +path = "lib.rs" + [dependencies] block = "0.1.6" dispatch = "0.2.0" @@ -18,4 +21,4 @@ url = "2.1.1" cloudkit = [] user-notifications = ["uuid"] webview = [] -enable-webview-downloading = [] +webview-downloading = [] diff --git a/appkit/src/alert.rs b/appkit/alert.rs similarity index 100% rename from appkit/src/alert.rs rename to appkit/alert.rs diff --git a/appkit/src/app/class.rs b/appkit/app/class.rs similarity index 100% rename from appkit/src/app/class.rs rename to appkit/app/class.rs diff --git a/appkit/src/app/controller.rs b/appkit/app/delegate.rs similarity index 79% rename from appkit/src/app/controller.rs rename to appkit/app/delegate.rs index e8d6482..e54170b 100644 --- a/appkit/src/app/controller.rs +++ b/appkit/app/delegate.rs @@ -15,7 +15,7 @@ use objc::runtime::{Class, Object, Sel}; use url::Url; use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString}; -use crate::app::traits::AppController; +use crate::app::traits::AppDelegate; use crate::constants::APP_PTR; use crate::error::AppKitError; use crate::printing::PrintSettings; @@ -24,9 +24,9 @@ use crate::user_activity::UserActivity; #[cfg(feature = "cloudkit")] use crate::cloudkit::share::CKShareMetaData; -/// A handy method for grabbing our `AppController` from the pointer. This is different from our +/// A handy method for grabbing our `AppDelegate` from the pointer. This is different from our /// standard `utils` version as this doesn't require `RefCell` backing. -fn app(this: &Object) -> &T { +fn app(this: &Object) -> &T { unsafe { let app_ptr: usize = *this.get_ivar(APP_PTR); let app = app_ptr as *const T; @@ -35,78 +35,78 @@ fn app(this: &Object) -> &T { } /// Fires when the Application Delegate receives a `applicationWillFinishLaunching` notification. -extern fn will_finish_launching(this: &Object, _: Sel, _: id) { +extern fn will_finish_launching(this: &Object, _: Sel, _: id) { app::(this).will_finish_launching(); } /// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification. -extern fn did_finish_launching(this: &Object, _: Sel, _: id) { +extern fn did_finish_launching(this: &Object, _: Sel, _: id) { app::(this).did_finish_launching(); } /// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification. -extern fn will_become_active(this: &Object, _: Sel, _: id) { +extern fn will_become_active(this: &Object, _: Sel, _: id) { app::(this).will_become_active(); } /// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification. -extern fn did_become_active(this: &Object, _: Sel, _: id) { +extern fn did_become_active(this: &Object, _: Sel, _: id) { app::(this).did_become_active(); } /// Fires when the Application Delegate receives a `applicationWillResignActive` notification. -extern fn will_resign_active(this: &Object, _: Sel, _: id) { +extern fn will_resign_active(this: &Object, _: Sel, _: id) { app::(this).will_resign_active(); } /// Fires when the Application Delegate receives a `applicationDidResignActive` notification. -extern fn did_resign_active(this: &Object, _: Sel, _: id) { +extern fn did_resign_active(this: &Object, _: Sel, _: id) { app::(this).did_resign_active(); } /// Fires when the Application Delegate receives a 'applicationShouldTerminate:` notification. -extern fn should_terminate(this: &Object, _: Sel, _: id) -> NSUInteger { +extern fn should_terminate(this: &Object, _: Sel, _: id) -> NSUInteger { app::(this).should_terminate().into() } /// Fires when the Application Delegate receives a `applicationWillTerminate:` notification. -extern fn will_terminate(this: &Object, _: Sel, _: id) { +extern fn will_terminate(this: &Object, _: Sel, _: id) { app::(this).will_terminate(); } /// Fires when the Application Delegate receives a `applicationWillHide:` notification. -extern fn will_hide(this: &Object, _: Sel, _: id) { +extern fn will_hide(this: &Object, _: Sel, _: id) { app::(this).will_hide(); } /// Fires when the Application Delegate receives a `applicationDidHide:` notification. -extern fn did_hide(this: &Object, _: Sel, _: id) { +extern fn did_hide(this: &Object, _: Sel, _: id) { app::(this).did_hide(); } /// Fires when the Application Delegate receives a `applicationWillUnhide:` notification. -extern fn will_unhide(this: &Object, _: Sel, _: id) { +extern fn will_unhide(this: &Object, _: Sel, _: id) { app::(this).will_unhide(); } /// Fires when the Application Delegate receives a `applicationDidUnhide:` notification. -extern fn did_unhide(this: &Object, _: Sel, _: id) { +extern fn did_unhide(this: &Object, _: Sel, _: id) { app::(this).did_unhide(); } /// Fires when the Application Delegate receives a `applicationWillUpdate:` notification. -extern fn will_update(this: &Object, _: Sel, _: id) { +extern fn will_update(this: &Object, _: Sel, _: id) { app::(this).will_update(); } /// Fires when the Application Delegate receives a `applicationDidUpdate:` notification. -extern fn did_update(this: &Object, _: Sel, _: id) { +extern fn did_update(this: &Object, _: Sel, _: id) { app::(this).did_update(); } /// Fires when the Application Delegate receives a /// `applicationShouldHandleReopen:hasVisibleWindows:` notification. -extern fn should_handle_reopen(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL { +extern fn should_handle_reopen(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL { match app::(this).should_handle_reopen(match has_visible_windows { YES => true, NO => false, @@ -118,7 +118,7 @@ extern fn should_handle_reopen(this: &Object, _: Sel, _: id, h } /// Fires when the application delegate receives a `applicationDockMenu:` request. -extern fn dock_menu(this: &Object, _: Sel, _: id) -> id { +extern fn dock_menu(this: &Object, _: Sel, _: id) -> id { match app::(this).dock_menu() { Some(mut menu) => &mut *menu.inner, None => nil @@ -126,19 +126,19 @@ extern fn dock_menu(this: &Object, _: Sel, _: id) -> id { } /// Fires when the application delegate receives a `application:willPresentError:` notification. -extern fn will_present_error(this: &Object, _: Sel, _: id, error: id) -> id { +extern fn will_present_error(this: &Object, _: Sel, _: id, error: id) -> id { let error = AppKitError::new(error); app::(this).will_present_error(error).into_nserror() } /// Fires when the application receives a `applicationDidChangeScreenParameters:` notification. -extern fn did_change_screen_parameters(this: &Object, _: Sel, _: id) { +extern fn did_change_screen_parameters(this: &Object, _: Sel, _: id) { app::(this).did_change_screen_parameters(); } /// Fires when the application receives a `application:willContinueUserActivityWithType:` /// notification. -extern fn will_continue_user_activity_with_type(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL { +extern fn will_continue_user_activity_with_type(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL { let activity = NSString::wrap(activity_type); match app::(this).will_continue_user_activity(activity.to_str()) { @@ -148,7 +148,7 @@ extern fn will_continue_user_activity_with_type(this: &Object, } /// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification. -extern fn continue_user_activity(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL { +extern fn continue_user_activity(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL { // @TODO: This needs to support restorable objects, but it involves a larger question about how // much `NSObject` wrapping we want to do here. For now, pass the handler for whenever it's // useful. @@ -165,7 +165,7 @@ extern fn continue_user_activity(this: &Object, _: Sel, _: id, /// Fires when the application receives a /// `application:didFailToContinueUserActivityWithType:error:` message. -extern fn failed_to_continue_user_activity(this: &Object, _: Sel, _: id, activity_type: id, error: id) { +extern fn failed_to_continue_user_activity(this: &Object, _: Sel, _: id, activity_type: id, error: id) { app::(this).failed_to_continue_user_activity( NSString::wrap(activity_type).to_str(), AppKitError::new(error) @@ -173,36 +173,36 @@ extern fn failed_to_continue_user_activity(this: &Object, _: S } /// Fires when the application receives a `application:didUpdateUserActivity:` message. -extern fn did_update_user_activity(this: &Object, _: Sel, _: id, activity: id) { +extern fn did_update_user_activity(this: &Object, _: Sel, _: id, activity: id) { let activity = UserActivity::with_inner(activity); app::(this).updated_user_activity(activity); } /// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message. -extern fn registered_for_remote_notifications(_this: &Object, _: Sel, _: id, _: id) { +extern fn registered_for_remote_notifications(_this: &Object, _: Sel, _: id, _: id) { } /// Fires when the application receives a `application:didFailToRegisterForRemoteNotificationsWithError:` message. -extern fn failed_to_register_for_remote_notifications(this: &Object, _: Sel, _: id, error: id) { +extern fn failed_to_register_for_remote_notifications(this: &Object, _: Sel, _: id, error: id) { app::(this).failed_to_register_for_remote_notifications(AppKitError::new(error)); } /// Fires when the application receives a `application:didReceiveRemoteNotification:` message. -extern fn did_receive_remote_notification(_this: &Object, _: Sel, _: id, _: id) { +extern fn did_receive_remote_notification(_this: &Object, _: Sel, _: id, _: id) { } /// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:` /// message. #[cfg(feature = "cloudkit")] -extern fn accepted_cloudkit_share(_this: &Object, _: Sel, _: id, metadata: id) { +extern fn accepted_cloudkit_share(_this: &Object, _: Sel, _: id, metadata: id) { let share = CKShareMetaData::with_inner(metadata); app::(this).user_accepted_cloudkit_share(share); } /// Fires when the application receives an `application:openURLs` message. -extern fn open_urls(this: &Object, _: Sel, _: id, file_urls: id) { +extern fn open_urls(this: &Object, _: Sel, _: id, file_urls: id) { let urls = NSArray::wrap(file_urls).map(|url| { let uri = NSString::wrap(unsafe { msg_send![url, absoluteString] @@ -215,7 +215,7 @@ extern fn open_urls(this: &Object, _: Sel, _: id, file_urls: i } /// Fires when the application receives an `application:openFileWithoutUI:` message. -extern fn open_file_without_ui(this: &Object, _: Sel, _: id, file: id) -> BOOL { +extern fn open_file_without_ui(this: &Object, _: Sel, _: id, file: id) -> BOOL { let filename = NSString::wrap(file); match app::(this).open_file_without_ui(filename.to_str()) { @@ -225,7 +225,7 @@ extern fn open_file_without_ui(this: &Object, _: Sel, _: id, f } /// Fired when the application receives an `applicationShouldOpenUntitledFile:` message. -extern fn should_open_untitled_file(this: &Object, _: Sel, _: id) -> BOOL { +extern fn should_open_untitled_file(this: &Object, _: Sel, _: id) -> BOOL { match app::(this).should_open_untitled_file() { true => YES, false => NO @@ -233,7 +233,7 @@ extern fn should_open_untitled_file(this: &Object, _: Sel, _: } /// Fired when the application receives an `applicationOpenUntitledFile:` message. -extern fn open_untitled_file(this: &Object, _: Sel, _: id) -> BOOL { +extern fn open_untitled_file(this: &Object, _: Sel, _: id) -> BOOL { match app::(this).open_untitled_file() { true => YES, false => NO @@ -241,7 +241,7 @@ extern fn open_untitled_file(this: &Object, _: Sel, _: id) -> } /// Fired when the application receives an `application:openTempFile:` message. -extern fn open_temp_file(this: &Object, _: Sel, _: id, filename: id) -> BOOL { +extern fn open_temp_file(this: &Object, _: Sel, _: id, filename: id) -> BOOL { let filename = NSString::wrap(filename); match app::(this).open_temp_file(filename.to_str()) { @@ -251,7 +251,7 @@ extern fn open_temp_file(this: &Object, _: Sel, _: id, filenam } /// Fired when the application receives an `application:printFile:` message. -extern fn print_file(this: &Object, _: Sel, _: id, file: id) -> BOOL { +extern fn print_file(this: &Object, _: Sel, _: id, file: id) -> BOOL { let filename = NSString::wrap(file); match app::(this).print_file(filename.to_str()) { @@ -262,7 +262,7 @@ extern fn print_file(this: &Object, _: Sel, _: id, file: id) - /// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:` /// message. -extern fn print_files(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger { +extern fn print_files(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger { let files = NSArray::wrap(files).map(|file| { NSString::wrap(file).to_str().to_string() }); @@ -277,14 +277,14 @@ extern fn print_files(this: &Object, _: Sel, _: id, files: id, } /// Called when the application's occlusion state has changed. -extern fn did_change_occlusion_state(this: &Object, _: Sel, _: id) { +extern fn did_change_occlusion_state(this: &Object, _: Sel, _: id) { app::(this).occlusion_state_changed(); } /// Called when the application receives an `application:delegateHandlesKey:` message. /// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the /// matter. -extern fn delegate_handles_key(this: &Object, _: Sel, _: id, key: id) -> BOOL { +extern fn delegate_handles_key(this: &Object, _: Sel, _: id, key: id) -> BOOL { let key = NSString::wrap(key); match app::(this).delegate_handles_key(key.to_str()) { @@ -295,13 +295,13 @@ extern fn delegate_handles_key(this: &Object, _: Sel, _: id, k /// Registers an `NSObject` application delegate, and configures it for the various callbacks and /// pointers we need to have. -pub(crate) fn register_app_controller_class() -> *const Class { +pub(crate) fn register_app_delegate_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!(NSObject); - let mut decl = ClassDecl::new("RSTAppController", superclass).unwrap(); + let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap(); decl.add_ivar::(APP_PTR); diff --git a/appkit/src/app/enums.rs b/appkit/app/enums.rs similarity index 100% rename from appkit/src/app/enums.rs rename to appkit/app/enums.rs diff --git a/appkit/src/app/mod.rs b/appkit/app/mod.rs similarity index 92% rename from appkit/src/app/mod.rs rename to appkit/app/mod.rs index ab520ef..3c352cb 100644 --- a/appkit/src/app/mod.rs +++ b/appkit/app/mod.rs @@ -12,13 +12,13 @@ use crate::menu::Menu; mod class; use class::register_app_class; -mod controller; -use controller::register_app_controller_class; +mod delegate; +use delegate::register_app_delegate_class; pub mod enums; pub mod traits; -pub use traits::{AppController, Dispatcher}; +pub use traits::{AppDelegate, Dispatcher}; /// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime, /// which is where our application instance lives. It also injects an `NSObject` subclass, @@ -54,7 +54,7 @@ impl App { } } -impl App where M: Send + Sync + 'static, T: AppController + Dispatcher { +impl App where T: AppDelegate + 'static { /// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation /// policies), injects an `NSObject` delegate wrapper, and retains everything on the /// Objective-C side of things. @@ -72,7 +72,7 @@ impl App where M: Send + Sync + 'static, T: AppController + Dispatch let app_delegate = Box::new(delegate); let objc_delegate = unsafe { - let delegate_class = register_app_controller_class::(); + let delegate_class = register_app_delegate_class::(); let delegate: id = msg_send![delegate_class, new]; let delegate_ptr: *const T = &*app_delegate; (&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize); @@ -89,6 +89,21 @@ impl App 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 App where M: Send + Sync + 'static, T: AppDelegate + Dispatcher { /// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate, /// and passing back through there. All messages are currently dispatched on the main thread. pub fn dispatch(message: M) { @@ -102,17 +117,4 @@ impl App where M: Send + Sync + 'static, T: AppController + Dispatch (&*delegate).on_message(message); }); } - - /// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called. - /// If you're wondering where to go from here... you need an `AppController` that implements - /// `did_finish_launching`. :) - pub fn run(&self) { - unsafe { - let current_app: id = msg_send![class!(NSRunningApplication), currentApplication]; - let _: () = msg_send![current_app, activateWithOptions:1<<1]; - let shared_app: id = msg_send![class!(RSTApplication), sharedApplication]; - let _: () = msg_send![shared_app, run]; - self.pool.drain(); - } - } } diff --git a/appkit/src/app/traits.rs b/appkit/app/traits.rs similarity index 99% rename from appkit/src/app/traits.rs rename to appkit/app/traits.rs index bb450e9..56db55a 100644 --- a/appkit/src/app/traits.rs +++ b/appkit/app/traits.rs @@ -21,11 +21,11 @@ pub trait Dispatcher { fn on_message(&self, _message: Self::Message) {} } -/// `AppController` is more or less `AppDelegate` from the Objective-C/Swift side, just named +/// `AppDelegate` is more or less `NSAppDelegate` from the Objective-C/Swift side, just named /// differently to fit in with the general naming scheme found within this framework. You can /// implement methods from this trait in order to respond to lifecycle events that the system will /// fire off. -pub trait AppController { +pub trait AppDelegate { /// Called right before the application will finish launching. You really, probably, want to do /// your setup in `did_finish_launching` unless you're sure of what you're doing. fn will_finish_launching(&self) {} diff --git a/appkit/build.rs b/appkit/build.rs index 538f2b1..5dad65c 100644 --- a/appkit/build.rs +++ b/appkit/build.rs @@ -6,6 +6,7 @@ fn main() { if std::env::var("TARGET").unwrap().contains("-apple") { println!("cargo:rustc-link-lib=framework=Foundation"); + println!("cargo:rustc-link-lib=framework=Cocoa"); println!("cargo:rustc-link-lib=framework=CoreGraphics"); println!("cargo:rustc-link-lib=framework=Security"); diff --git a/appkit/src/bundle.rs b/appkit/bundle.rs similarity index 100% rename from appkit/src/bundle.rs rename to appkit/bundle.rs diff --git a/appkit/src/button.rs b/appkit/button.rs similarity index 100% rename from appkit/src/button.rs rename to appkit/button.rs diff --git a/appkit/src/cloudkit/mod.rs b/appkit/cloudkit/mod.rs similarity index 100% rename from appkit/src/cloudkit/mod.rs rename to appkit/cloudkit/mod.rs diff --git a/appkit/src/cloudkit/share.rs b/appkit/cloudkit/share.rs similarity index 100% rename from appkit/src/cloudkit/share.rs rename to appkit/cloudkit/share.rs diff --git a/appkit/src/collection_view/mod.rs b/appkit/collection_view/mod.rs similarity index 100% rename from appkit/src/collection_view/mod.rs rename to appkit/collection_view/mod.rs diff --git a/appkit/src/collection_view/traits.rs b/appkit/collection_view/traits.rs similarity index 100% rename from appkit/src/collection_view/traits.rs rename to appkit/collection_view/traits.rs diff --git a/appkit/src/color.rs b/appkit/color.rs similarity index 100% rename from appkit/src/color.rs rename to appkit/color.rs diff --git a/appkit/src/constants.rs b/appkit/constants.rs similarity index 79% rename from appkit/src/constants.rs rename to appkit/constants.rs index 9963966..0ac76cc 100644 --- a/appkit/src/constants.rs +++ b/appkit/constants.rs @@ -4,7 +4,7 @@ pub(crate) static APP_PTR: &str = "rstAppPtr"; pub(crate) static BACKGROUND_COLOR: &str = "rstBackgroundColor"; pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr"; -pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr"; +pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr"; #[cfg(feature = "webview")] pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig"; @@ -14,5 +14,3 @@ pub(crate) static WEBVIEW_VAR: &str = "rstWebView"; #[cfg(feature = "webview")] pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr"; - -pub(crate) static WINDOW_CONTROLLER_PTR: &str = "rstWindowController"; diff --git a/appkit/src/dragdrop.rs b/appkit/dragdrop.rs similarity index 100% rename from appkit/src/dragdrop.rs rename to appkit/dragdrop.rs diff --git a/appkit/src/error.rs b/appkit/error.rs similarity index 100% rename from appkit/src/error.rs rename to appkit/error.rs diff --git a/appkit/src/events.rs b/appkit/events.rs similarity index 100% rename from appkit/src/events.rs rename to appkit/events.rs diff --git a/appkit/src/filesystem/enums.rs b/appkit/filesystem/enums.rs similarity index 100% rename from appkit/src/filesystem/enums.rs rename to appkit/filesystem/enums.rs diff --git a/appkit/src/filesystem/manager.rs b/appkit/filesystem/manager.rs similarity index 100% rename from appkit/src/filesystem/manager.rs rename to appkit/filesystem/manager.rs diff --git a/appkit/src/filesystem/mod.rs b/appkit/filesystem/mod.rs similarity index 100% rename from appkit/src/filesystem/mod.rs rename to appkit/filesystem/mod.rs diff --git a/appkit/src/filesystem/save.rs b/appkit/filesystem/save.rs similarity index 100% rename from appkit/src/filesystem/save.rs rename to appkit/filesystem/save.rs diff --git a/appkit/src/filesystem/select.rs b/appkit/filesystem/select.rs similarity index 100% rename from appkit/src/filesystem/select.rs rename to appkit/filesystem/select.rs diff --git a/appkit/src/filesystem/traits.rs b/appkit/filesystem/traits.rs similarity index 100% rename from appkit/src/filesystem/traits.rs rename to appkit/filesystem/traits.rs diff --git a/appkit/src/foundation/array.rs b/appkit/foundation/array.rs similarity index 100% rename from appkit/src/foundation/array.rs rename to appkit/foundation/array.rs diff --git a/appkit/src/foundation/autoreleasepool.rs b/appkit/foundation/autoreleasepool.rs similarity index 100% rename from appkit/src/foundation/autoreleasepool.rs rename to appkit/foundation/autoreleasepool.rs diff --git a/appkit/src/foundation/dictionary.rs b/appkit/foundation/dictionary.rs similarity index 100% rename from appkit/src/foundation/dictionary.rs rename to appkit/foundation/dictionary.rs diff --git a/appkit/src/foundation/geometry.rs b/appkit/foundation/geometry.rs similarity index 100% rename from appkit/src/foundation/geometry.rs rename to appkit/foundation/geometry.rs diff --git a/appkit/src/foundation/mod.rs b/appkit/foundation/mod.rs similarity index 100% rename from appkit/src/foundation/mod.rs rename to appkit/foundation/mod.rs diff --git a/appkit/src/foundation/string.rs b/appkit/foundation/string.rs similarity index 100% rename from appkit/src/foundation/string.rs rename to appkit/foundation/string.rs diff --git a/appkit/src/geometry.rs b/appkit/geometry.rs similarity index 97% rename from appkit/src/geometry.rs rename to appkit/geometry.rs index 6466655..8d595d9 100644 --- a/appkit/src/geometry.rs +++ b/appkit/geometry.rs @@ -3,6 +3,7 @@ use crate::foundation::{CGRect, CGPoint, CGSize}; /// A struct that represents a box - top, left, width and height. +#[derive(Copy, Clone, Debug)] pub struct Rect { /// Distance from the top, in points. pub top: f64, diff --git a/appkit/src/layout/constraint.rs b/appkit/layout/constraint.rs similarity index 100% rename from appkit/src/layout/constraint.rs rename to appkit/layout/constraint.rs diff --git a/appkit/src/layout/dimension.rs b/appkit/layout/dimension.rs similarity index 100% rename from appkit/src/layout/dimension.rs rename to appkit/layout/dimension.rs diff --git a/appkit/src/layout/horizontal.rs b/appkit/layout/horizontal.rs similarity index 100% rename from appkit/src/layout/horizontal.rs rename to appkit/layout/horizontal.rs diff --git a/appkit/src/layout/mod.rs b/appkit/layout/mod.rs similarity index 100% rename from appkit/src/layout/mod.rs rename to appkit/layout/mod.rs diff --git a/appkit/src/layout/traits.rs b/appkit/layout/traits.rs similarity index 91% rename from appkit/src/layout/traits.rs rename to appkit/layout/traits.rs index 0977433..4a4faba 100644 --- a/appkit/src/layout/traits.rs +++ b/appkit/layout/traits.rs @@ -9,7 +9,7 @@ pub trait Layout { /// Returns a reference to the backing Objective-C layer. This is optional, as we try to keep /// the general lazy-loading approach Cocoa has. This may change in the future, and in general /// this shouldn't affect your code too much (if at all). - fn get_backing_node(&self) -> Option>; + fn get_backing_node(&self) -> ShareId; /// This trait should implement adding a view to the subview tree for a given view. fn add_subview(&self, _view: &V); diff --git a/appkit/src/layout/vertical.rs b/appkit/layout/vertical.rs similarity index 100% rename from appkit/src/layout/vertical.rs rename to appkit/layout/vertical.rs diff --git a/appkit/src/lib.rs b/appkit/lib.rs similarity index 92% rename from appkit/src/lib.rs rename to appkit/lib.rs index e7cf31b..24fb23e 100644 --- a/appkit/src/lib.rs +++ b/appkit/lib.rs @@ -44,7 +44,7 @@ pub mod printing; pub mod toolbar; pub mod user_activity; pub mod utils; -pub mod view; +//pub mod view; #[cfg(feature = "webview")] pub mod webview; @@ -55,7 +55,7 @@ pub mod window; pub use url; pub mod prelude { - pub use crate::app::{App, AppController, Dispatcher}; + pub use crate::app::{App, AppDelegate, Dispatcher}; pub use crate::layout::LayoutConstraint; @@ -69,7 +69,7 @@ pub mod prelude { pub use crate::networking::URLRequest; pub use crate::window::{ - Window, WindowController, WindowHandle + Window, /*WindowController,*/ WindowDelegate }; #[cfg(feature = "webview")] @@ -77,5 +77,5 @@ pub mod prelude { WebView, WebViewConfig, WebViewController }; - pub use crate::view::{View, ViewHandle, ViewController}; + //pub use crate::view::{View, ViewController, ViewDelegate}; } diff --git a/appkit/src/menu/item.rs b/appkit/menu/item.rs similarity index 100% rename from appkit/src/menu/item.rs rename to appkit/menu/item.rs diff --git a/appkit/src/menu/menu.rs b/appkit/menu/menu.rs similarity index 100% rename from appkit/src/menu/menu.rs rename to appkit/menu/menu.rs diff --git a/appkit/src/menu/mod.rs b/appkit/menu/mod.rs similarity index 100% rename from appkit/src/menu/mod.rs rename to appkit/menu/mod.rs diff --git a/appkit/src/networking/mod.rs b/appkit/networking/mod.rs similarity index 100% rename from appkit/src/networking/mod.rs rename to appkit/networking/mod.rs diff --git a/appkit/src/notifications/enums.rs b/appkit/notifications/enums.rs similarity index 100% rename from appkit/src/notifications/enums.rs rename to appkit/notifications/enums.rs diff --git a/appkit/src/notifications/mod.rs b/appkit/notifications/mod.rs similarity index 100% rename from appkit/src/notifications/mod.rs rename to appkit/notifications/mod.rs diff --git a/appkit/src/notifications/notifications.rs b/appkit/notifications/notifications.rs similarity index 100% rename from appkit/src/notifications/notifications.rs rename to appkit/notifications/notifications.rs diff --git a/appkit/src/pasteboard/mod.rs b/appkit/pasteboard/mod.rs similarity index 100% rename from appkit/src/pasteboard/mod.rs rename to appkit/pasteboard/mod.rs diff --git a/appkit/src/pasteboard/types.rs b/appkit/pasteboard/types.rs similarity index 100% rename from appkit/src/pasteboard/types.rs rename to appkit/pasteboard/types.rs diff --git a/appkit/src/printing/enums.rs b/appkit/printing/enums.rs similarity index 100% rename from appkit/src/printing/enums.rs rename to appkit/printing/enums.rs diff --git a/appkit/src/printing/mod.rs b/appkit/printing/mod.rs similarity index 100% rename from appkit/src/printing/mod.rs rename to appkit/printing/mod.rs diff --git a/appkit/src/printing/settings.rs b/appkit/printing/settings.rs similarity index 100% rename from appkit/src/printing/settings.rs rename to appkit/printing/settings.rs diff --git a/appkit/src/view/controller.rs b/appkit/src/view/controller.rs deleted file mode 100644 index aa9ba46..0000000 --- a/appkit/src/view/controller.rs +++ /dev/null @@ -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(this: &mut Object, _: Sel) { - unsafe { - let zero: CGRect = Rect::zero().into(); - let view: id = msg_send![register_view_class::(), 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() -> *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::(VIEW_CONTROLLER_PTR); - - // NSViewController - decl.add_method(sel!(loadView), load_view:: as extern fn(&mut Object, _)); - - VIEW_CLASS = decl.register(); - }); - - unsafe { VIEW_CLASS } -} diff --git a/appkit/src/webview/config.rs b/appkit/src/webview/config.rs deleted file mode 100644 index b1fa81c..0000000 --- a/appkit/src/webview/config.rs +++ /dev/null @@ -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 -} - -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 - } - } -} diff --git a/appkit/src/webview/mod.rs b/appkit/src/webview/mod.rs deleted file mode 100644 index ce504d2..0000000 --- a/appkit/src/webview/mod.rs +++ /dev/null @@ -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; diff --git a/appkit/src/webview/webview.rs b/appkit/src/webview/webview.rs deleted file mode 100644 index 51a4308..0000000 --- a/appkit/src/webview/webview.rs +++ /dev/null @@ -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> -} - -impl WebViewInner { - pub fn configure(&mut self, controller: &T) { - self.controller = Some(unsafe { - let view_controller: id = msg_send![register_controller_class::(), 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>); - -impl WebView { - pub fn configure(&self, controller: &T) { - { - let mut webview = self.0.borrow_mut(); - webview.configure(controller); - } - - //controller.did_load(); - } - - pub fn get_handle(&self) -> Option> { - 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); - } -} diff --git a/appkit/src/window/config.rs b/appkit/src/window/config.rs deleted file mode 100644 index b7dfc02..0000000 --- a/appkit/src/window/config.rs +++ /dev/null @@ -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); - -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) - }) - } -} diff --git a/appkit/src/window/controller.rs b/appkit/src/window/controller.rs deleted file mode 100644 index 5f0230e..0000000 --- a/appkit/src/window/controller.rs +++ /dev/null @@ -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(this: &Object, _: Sel, _: id) { - let window = load::(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() -> *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::(WINDOW_CONTROLLER_PTR); - - // Subclassed methods - - // NSWindowDelegate methods - decl.add_method(sel!(windowWillClose:), will_close:: as extern fn(&Object, _, _)); - - DELEGATE_CLASS = decl.register(); - }); - - unsafe { - DELEGATE_CLASS - } -} diff --git a/appkit/src/window/enums.rs b/appkit/src/window/enums.rs deleted file mode 100644 index 822f6e9..0000000 --- a/appkit/src/window/enums.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! Enums used in Window construction and handling. - -pub enum WindowTitleVisibility { - Visible, - Hidden -} - -impl From for usize { - fn from(visibility: WindowTitleVisibility) -> usize { - match visibility { - WindowTitleVisibility::Visible => 0, - WindowTitleVisibility::Hidden => 1 - } - } -} diff --git a/appkit/src/window/handle.rs b/appkit/src/window/handle.rs deleted file mode 100644 index b2d0f1a..0000000 --- a/appkit/src/window/handle.rs +++ /dev/null @@ -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>); - -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>(&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(&self, toolbar: &Toolbar) { - 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(&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]; - } - } - } -} diff --git a/appkit/src/window/mod.rs b/appkit/src/window/mod.rs deleted file mode 100644 index bb25165..0000000 --- a/appkit/src/window/mod.rs +++ /dev/null @@ -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); -//! -//! 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 { - internal_callback_ptr: *const RefCell, - pub objc_controller: WindowHandle, - pub controller: Rc> -} - -impl Window 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::(); - 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(&self, toolbar: &Toolbar) { - self.objc_controller.set_toolbar(toolbar); - } - - /// Sets the content view controller for the window. - pub fn set_content_view_controller(&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 Drop for Window { - /// 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); - } - } -} diff --git a/appkit/src/window/window.rs b/appkit/src/window/window.rs deleted file mode 100644 index 773b913..0000000 --- a/appkit/src/window/window.rs +++ /dev/null @@ -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 { - internal_callback_ptr: *const RefCell, - pub objc_controller: WindowHandle, - pub controller: Rc> -} - -impl Window 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::(); - 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(&self, toolbar: &Toolbar) { - self.objc_controller.set_toolbar(toolbar); - } - - /// Sets the content view controller for the window. - pub fn set_content_view_controller(&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 Drop for Window { - /// 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); - } - } -} diff --git a/appkit/src/toolbar/class.rs b/appkit/toolbar/class.rs similarity index 100% rename from appkit/src/toolbar/class.rs rename to appkit/toolbar/class.rs diff --git a/appkit/src/toolbar/handle.rs b/appkit/toolbar/handle.rs similarity index 100% rename from appkit/src/toolbar/handle.rs rename to appkit/toolbar/handle.rs diff --git a/appkit/src/toolbar/item.rs b/appkit/toolbar/item.rs similarity index 100% rename from appkit/src/toolbar/item.rs rename to appkit/toolbar/item.rs diff --git a/appkit/src/toolbar/mod.rs b/appkit/toolbar/mod.rs similarity index 100% rename from appkit/src/toolbar/mod.rs rename to appkit/toolbar/mod.rs diff --git a/appkit/src/toolbar/toolbar.rs b/appkit/toolbar/toolbar.rs similarity index 100% rename from appkit/src/toolbar/toolbar.rs rename to appkit/toolbar/toolbar.rs diff --git a/appkit/src/toolbar/traits.rs b/appkit/toolbar/traits.rs similarity index 100% rename from appkit/src/toolbar/traits.rs rename to appkit/toolbar/traits.rs diff --git a/appkit/src/toolbar/types.rs b/appkit/toolbar/types.rs similarity index 100% rename from appkit/src/toolbar/types.rs rename to appkit/toolbar/types.rs diff --git a/appkit/src/user_activity.rs b/appkit/user_activity.rs similarity index 100% rename from appkit/src/user_activity.rs rename to appkit/user_activity.rs diff --git a/appkit/src/utils.rs b/appkit/utils.rs similarity index 100% rename from appkit/src/utils.rs rename to appkit/utils.rs diff --git a/appkit/src/view/class.rs b/appkit/view/class.rs similarity index 67% rename from appkit/src/view/class.rs rename to appkit/view/class.rs index cad6e19..0e7d2ec 100644 --- a/appkit/src/view/class.rs +++ b/appkit/view/class.rs @@ -12,13 +12,13 @@ use std::sync::Once; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel, BOOL}; -use objc::{msg_send, sel, sel_impl}; +use objc::{class, msg_send, sel, sel_impl}; use objc_id::Id; use crate::foundation::{id, nil, YES, NO, NSUInteger}; -use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR}; +use crate::constants::{BACKGROUND_COLOR, VIEW_DELEGATE_PTR}; use crate::dragdrop::DragInfo; -use crate::view::traits::ViewController; +use crate::view::traits::ViewDelegate; use crate::utils::load; /// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though. @@ -27,6 +27,7 @@ extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL { } /// Used for handling background colors in layer backed views (which is the default here). +/// @TODO: This could be more efficient, I think. extern fn update_layer(this: &Object, _: Sel) { unsafe { let background_color: id = *this.get_ivar(BACKGROUND_COLOR); @@ -39,8 +40,8 @@ extern fn update_layer(this: &Object, _: Sel) { } /// Called when a drag/drop operation has entered this view. -extern fn dragging_entered(this: &mut Object, _: Sel, info: id) -> NSUInteger { - let view = load::(this, VIEW_CONTROLLER_PTR); +extern fn dragging_entered(this: &mut Object, _: Sel, info: id) -> NSUInteger { + let view = load::(this, VIEW_DELEGATE_PTR); let response = { let v = view.borrow(); @@ -55,8 +56,8 @@ extern fn dragging_entered(this: &mut Object, _: Sel, info: i } /// Called when a drag/drop operation has entered this view. -extern fn prepare_for_drag_operation(this: &mut Object, _: Sel, info: id) -> BOOL { - let view = load::(this, VIEW_CONTROLLER_PTR); +extern fn prepare_for_drag_operation(this: &mut Object, _: Sel, info: id) -> BOOL { + let view = load::(this, VIEW_DELEGATE_PTR); let response = { let v = view.borrow(); @@ -74,8 +75,8 @@ extern fn prepare_for_drag_operation(this: &mut Object, _: Se } /// Called when a drag/drop operation has entered this view. -extern fn perform_drag_operation(this: &mut Object, _: Sel, info: id) -> BOOL { - let view = load::(this, VIEW_CONTROLLER_PTR); +extern fn perform_drag_operation(this: &mut Object, _: Sel, info: id) -> BOOL { + let view = load::(this, VIEW_DELEGATE_PTR); let response = { let v = view.borrow(); @@ -93,8 +94,8 @@ extern fn perform_drag_operation(this: &mut Object, _: Sel, i } /// Called when a drag/drop operation has entered this view. -extern fn conclude_drag_operation(this: &mut Object, _: Sel, info: id) { - let view = load::(this, VIEW_CONTROLLER_PTR); +extern fn conclude_drag_operation(this: &mut Object, _: Sel, info: id) { + let view = load::(this, VIEW_DELEGATE_PTR); { let v = view.borrow(); @@ -107,8 +108,8 @@ extern fn conclude_drag_operation(this: &mut Object, _: Sel, } /// Called when a drag/drop operation has entered this view. -extern fn dragging_exited(this: &mut Object, _: Sel, info: id) { - let view = load::(this, VIEW_CONTROLLER_PTR); +extern fn dragging_exited(this: &mut Object, _: Sel, info: id) { + let view = load::(this, VIEW_DELEGATE_PTR); { let v = view.borrow(); @@ -120,19 +121,41 @@ extern fn dragging_exited(this: &mut Object, _: Sel, info: id Rc::into_raw(view); } -/// Injects an `NSView` subclass, with some callback and pointer ivars for what we -/// need to do. -pub(crate) fn register_view_class() -> *const Class { +/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we +/// have separate classes here since we don't want to waste cycles on methods that will never be +/// used if there's no delegates. +pub(crate) fn register_view_class() -> *const Class { static mut VIEW_CLASS: *const Class = 0 as *const Class; static INIT: Once = Once::new(); INIT.call_once(|| unsafe { - let superclass = Class::get("NSView").unwrap(); + let superclass = class!(NSView); let mut decl = ClassDecl::new("RSTView", superclass).unwrap(); + decl.add_ivar::(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() -> *const Class { + static mut VIEW_CLASS: *const Class = 0 as *const Class; + static INIT: Once = Once::new(); + + INIT.call_once(|| unsafe { + let superclass = class!(NSView); + let mut decl = ClassDecl::new("RSTViewWithDelegate", superclass).unwrap(); + // A pointer to the "view controller" on the Rust side. It's expected that this doesn't // move. - decl.add_ivar::(VIEW_CONTROLLER_PTR); + decl.add_ivar::(VIEW_DELEGATE_PTR); decl.add_ivar::(BACKGROUND_COLOR); decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL); diff --git a/appkit/view/controller/class.rs b/appkit/view/controller/class.rs new file mode 100644 index 0000000..4f7fad1 --- /dev/null +++ b/appkit/view/controller/class.rs @@ -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(this: &mut Object, _: Sel) { + let controller = load::(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(this: &mut Object, _: Sel) { + let controller = load::(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(this: &mut Object, _: Sel) { + let controller = load::(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(this: &mut Object, _: Sel) { + let controller = load::(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() -> *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::(VIEW_DELEGATE_PTR); + + // NSViewDelegate + decl.add_method(sel!(viewWillAppear), will_appear:: as extern fn(&mut Object, _)); + decl.add_method(sel!(viewDidAppear), did_appear:: as extern fn(&mut Object, _)); + decl.add_method(sel!(viewWillDisappear), will_disappear:: as extern fn(&mut Object, _)); + decl.add_method(sel!(viewDidDisappear), did_disappear:: as extern fn(&mut Object, _)); + + VIEW_CLASS = decl.register(); + }); + + unsafe { VIEW_CLASS } +} diff --git a/appkit/view/controller/mod.rs b/appkit/view/controller/mod.rs new file mode 100644 index 0000000..85570c8 --- /dev/null +++ b/appkit/view/controller/mod.rs @@ -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, + pub view: Box +} + +impl ViewController { + pub fn new(view: T) -> Self { + let view = Box::new(view); + + let objc = unsafe { + let vc: id = msg_send![register_view_controller_class::(), 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 + } + } +} diff --git a/appkit/view/mod.rs b/appkit/view/mod.rs new file mode 100644 index 0000000..abec299 --- /dev/null +++ b/appkit/view/mod.rs @@ -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 { + /// A pointer to the Objective-C runtime view controller. + pub objc: ShareId, + + /// An internal callback pointer that we use in delegate loopbacks. Default implementations + /// don't require this. + internal_callback_ptr: Option<*const RefCell>, + + /// A pointer to the delegate for this view. + pub delegate: Option>>, + + /// 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::>().into(); + + let _: () = msg_send![&*self.objc, registerForDraggedTypes:types.into_inner()]; + } + } + + /// Given a subview, adds it to this view. + pub fn add_subview(&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 View 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 { + 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::(), 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 { + self.objc.clone() + } + + fn add_subview(&self, _: &V) {} +} + +impl Drop for View { + /// 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); + } + } + } +} diff --git a/appkit/src/view/traits.rs b/appkit/view/traits.rs similarity index 60% rename from appkit/src/view/traits.rs rename to appkit/view/traits.rs index 8ba7b67..f215acf 100644 --- a/appkit/src/view/traits.rs +++ b/appkit/view/traits.rs @@ -1,16 +1,25 @@ //! Various traits used for Views. use crate::dragdrop::{DragInfo, DragOperation}; -use crate::view::ViewHandle; +use crate::view::View; -pub trait ViewController { - /// Where possible, we try to respect the lazy-ish-loading of macOS/iOS systems. This hook - /// notifies you when the `View` has actually loaded into memory, and you're free to do things - /// with it. - /// - /// Note that you can trigger the view to load earlier, if need be... and in many cases it's - /// fine. This is a platform-specific detail you may want to read up on. :) - fn did_load(&mut self, _view: ViewHandle) {} +pub trait ViewDelegate { + /// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to + /// store and use repeatedly, but it's not thread safe - any UI calls must be made from the + /// main thread! + fn did_load(&mut self, _view: View) {} + + /// Called when this is about to be added to the view heirarchy. + fn will_appear(&self) {} + + /// Called after this has been added to the view heirarchy. + fn did_appear(&self) {} + + /// Called when this is about to be removed from the view heirarchy. + fn will_disappear(&self) {} + + /// Called when this has been removed from the view heirarchy. + fn did_disappear(&self) {} /// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform. fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None } diff --git a/appkit/src/webview/action.rs b/appkit/webview/actions.rs similarity index 57% rename from appkit/src/webview/action.rs rename to appkit/webview/actions.rs index 334a5d6..65af724 100644 --- a/appkit/src/webview/action.rs +++ b/appkit/webview/actions.rs @@ -1,36 +1,11 @@ //! Implements wrappers around `WKNavigationAction` and `WKNavigationActionPolicy`. -use cocoa::base::{id, YES, NO}; -use cocoa::foundation::NSInteger; - -use objc::runtime::BOOL; use objc::{msg_send, sel, sel_impl}; +use crate::foundation::{id, BOOL, YES, NO, NSInteger}; use crate::networking::URLRequest; -pub enum NavigationType { - LinkActivated, - FormSubmitted, - BackForward, - Reload, - FormResubmitted, - Other -} - -impl From for NavigationType { - fn from(i: NSInteger) -> Self { - match i { - -1 => NavigationType::Other, - 0 => NavigationType::LinkActivated, - 1 => NavigationType::FormSubmitted, - 2 => NavigationType::BackForward, - 3 => NavigationType::Reload, - 4 => NavigationType::FormResubmitted, - e => { panic!("Unsupported navigation type: {}", e); } - } - } -} - +#[derive(Debug)] pub struct NavigationAction { pub navigation_type: NavigationType, pub request: URLRequest @@ -51,20 +26,7 @@ impl NavigationAction { } } -pub enum NavigationPolicy { - Cancel, - Allow -} - -impl Into for NavigationPolicy { - fn into(self) -> NSInteger { - match self { - NavigationPolicy::Cancel => 0, - NavigationPolicy::Allow => 1 - } - } -} - +#[derive(Copy, Clone, Debug, Default)] pub struct NavigationResponse { pub can_show_mime_type: bool } @@ -80,25 +42,8 @@ impl NavigationResponse { } } -pub enum NavigationResponsePolicy { - Cancel = 0, - Allow = 1, - // This is a private API! - BecomeDownload = 2 -} - -impl Into for NavigationResponsePolicy { - fn into(self) -> NSInteger { - match self { - NavigationResponsePolicy::Cancel => 0, - NavigationResponsePolicy::Allow => 1, - NavigationResponsePolicy::BecomeDownload => 2 - } - } -} - -#[derive(Debug, Default)] +#[derive(Copy, Clone, Debug, Default)] pub struct OpenPanelParameters { pub allows_directories: bool, pub allows_multiple_selection: bool diff --git a/appkit/webview/config.rs b/appkit/webview/config.rs new file mode 100644 index 0000000..420d85d --- /dev/null +++ b/appkit/webview/config.rs @@ -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, + pub handlers: Vec +} + +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]; + } + } +} diff --git a/appkit/src/webview/controller.rs b/appkit/webview/controller.rs similarity index 89% rename from appkit/src/webview/controller.rs rename to appkit/webview/controller.rs index b4ebd47..2ef0e60 100644 --- a/appkit/src/webview/controller.rs +++ b/appkit/webview/controller.rs @@ -7,18 +7,16 @@ use std::ffi::c_void; use block::Block; -use cocoa::base::{id, nil, YES, NO}; -use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSArray, NSInteger}; - use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, nil, YES, NO, CGRect, NSString, NSArray, NSInteger}; use crate::constants::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; +use crate::geometry::Rect; use crate::view::traits::ViewController; use crate::webview::action::{NavigationAction, NavigationResponse}; use crate::webview::traits::WebViewController; -use crate::utils::str_from; /// Loads and configures ye old WKWebView/View for this controller. extern fn load_view(this: &mut Object, _: Sel) { @@ -26,12 +24,12 @@ extern fn load_view(this: &mut Object, _: let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR); // Technically private! - #[cfg(feature = "enable-webview-downloading")] + #[cfg(feature = "webview-downloading")] let process_pool: id = msg_send![configuration, processPool]; - #[cfg(feature = "enable-webview-downloading")] + #[cfg(feature = "webview-downloading")] let _: () = msg_send![process_pool, _setDownloadDelegate:&*this]; - let zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(1000., 600.)); + let zero: CGRect = Rect::zero().into(); let webview_alloc: id = msg_send![class!(WKWebView), alloc]; let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration]; let _: () = msg_send![webview, setWantsLayer:YES]; @@ -59,7 +57,7 @@ extern fn view_did_load(this: &Object, _: /// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your /// `WebViewController`, where you should handle the event. extern fn alert(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) { - let alert = str_from(s); + let alert = NSString::wrap(s).to_str(); println!("Alert: {}", alert); // @TODO: This is technically (I think?) a private method, and there's some other dance that @@ -85,8 +83,8 @@ extern fn alert(_: &Object, _: Sel, _: id, s: id /// Fires when a message has been passed from the underlying `WKWebView`. extern fn on_message(this: &Object, _: Sel, _: id, script_message: id) { unsafe { - let name = str_from(msg_send![script_message, name]); - let body = str_from(msg_send![script_message, body]); + let name = NSString::wrap(msg_send![script_message, name]).to_str(); + let body = NSString::wrap(msg_send![script_message, body]).to_str(); let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); let webview = ptr as *const T; @@ -140,13 +138,12 @@ extern fn run_open_panel(this: &Object, _: Sel, match urls { Some(u) => { - let nsurls: Vec = u.iter().map(|s| { - let s = NSString::alloc(nil).init_str(&s); + let nsurls: NSArray = u.iter().map(|s| { + let s = NSString::new(&s); msg_send![class!(NSURL), URLWithString:s] - }).collect(); + }).collect::>().into(); - let array = NSArray::arrayWithObjects(nil, &nsurls); - (*handler).call((array,)); + (*handler).call((nsurls.into_inner(),)); }, None => { (*handler).call((nil,)); } @@ -157,7 +154,7 @@ extern fn run_open_panel(this: &Object, _: Sel, /// Called when a download has been initiated in the WebView, and when the navigation policy /// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private /// API. -#[cfg(feature = "enable-webview-downloading")] +#[cfg(feature = "webview-downloading")] extern fn handle_download(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) { let webview = unsafe { let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); @@ -166,19 +163,19 @@ extern fn handle_download(this: &Object, _: Sel, }; let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>; - let filename = str_from(suggested_filename); + let filename = NSString::wrap(suggested_filename).to_str(); webview.run_save_panel(filename, move |can_overwrite, path| unsafe { if path.is_none() { let _: () = msg_send![download, cancel]; } - let path = NSString::alloc(nil).init_str(&path.unwrap()); + let path = NSString::new(&path.unwrap()); (*handler).call((match can_overwrite { true => YES, false => NO - }, path)); + }, path.into_inner())); }); } @@ -218,7 +215,7 @@ pub fn register_controller_class< // WKDownloadDelegate is a private class on macOS that handles downloading (saving) files. // It's absurd that this is still private in 2020. This probably couldn't get into the app // store, so... screw it, fine for now. - #[cfg(feature = "enable-webview-downloading")] + #[cfg(feature = "webview-downloading")] decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download:: as extern fn(&Object, _, id, id, usize)); VIEW_CLASS = decl.register(); diff --git a/appkit/webview/enums.rs b/appkit/webview/enums.rs new file mode 100644 index 0000000..f0427ea --- /dev/null +++ b/appkit/webview/enums.rs @@ -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 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 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 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 for NSInteger { + fn from(at: InjectAt) -> Self { + match at { + InjectAt::Start => 0, + InjectAt::End => 1 + } + } +} diff --git a/appkit/src/view/handle.rs b/appkit/webview/handle.rs similarity index 54% rename from appkit/src/view/handle.rs rename to appkit/webview/handle.rs index 5694f7f..e2a889a 100644 --- a/appkit/src/view/handle.rs +++ b/appkit/webview/handle.rs @@ -1,5 +1,5 @@ -//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your -//! `ViewController::did_load()` method. This method is safe to store and use, however as it's +//! A `WebViewHandle` represents an underlying `WKWebView`. You're passed a reference to one during your +//! `WebViewController::did_load()` method. This method is safe to store and use, however as it's //! UI-specific it's not thread safe. //! //! You can use this struct to configure how a view should look and layout. It implements @@ -16,10 +16,10 @@ use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension} use crate::pasteboard::PasteboardType; /// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this -/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that +/// instead of a stock `WKWebView` for easier recordkeeping, since it'll need to hold the `WKWebView` on that /// side anyway. #[derive(Debug, Default, Clone)] -pub struct ViewHandle { +pub struct WebViewHandle { /// A pointer to the Objective-C runtime view controller. pub objc: Option>, @@ -48,13 +48,13 @@ pub struct ViewHandle { pub center_y: LayoutAnchorY } -impl ViewHandle { +impl WebViewHandle { pub(crate) fn new(object: ShareId) -> Self { let view: id = unsafe { msg_send![&*object, view] }; - ViewHandle { + WebViewHandle { objc: Some(object), top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }), leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }), @@ -66,46 +66,4 @@ impl ViewHandle { center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }), } } - - /// Call this to set the background color for the backing layer. - pub fn set_background_color(&self, color: Color) { - if let Some(objc) = &self.objc { - unsafe { - let view: id = msg_send![*objc, view]; - (*view).set_ivar(BACKGROUND_COLOR, color.into_platform_specific_color()); - let _: () = msg_send![view, setNeedsDisplay:YES]; - } - } - } - - /// Register this view for drag and drop operations. - pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { - if let Some(objc) = &self.objc { - unsafe { - let types: NSArray = types.into_iter().map(|t| { - // This clone probably doesn't need to be here, but it should also be cheap as - // this is just an enum... and this is not an oft called method. - let x: NSString = t.clone().into(); - x.into_inner() - }).collect::>().into(); - - let view: id = msg_send![*objc, view]; - let _: () = msg_send![view, registerForDraggedTypes:types.into_inner()]; - } - } - } - - pub fn add_subview(&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]; - } - } - } - } } diff --git a/appkit/src/view/mod.rs b/appkit/webview/mod.rs similarity index 59% rename from appkit/src/view/mod.rs rename to appkit/webview/mod.rs index 01583ce..9b3e068 100644 --- a/appkit/src/view/mod.rs +++ b/appkit/webview/mod.rs @@ -1,70 +1,48 @@ -//! This module implements tooling for constructing Views. Notably, it provides the following: +//! Implements a WebView, which wraps a number of different classes/delegates/controllers into one +//! useful interface. This encompasses... //! -//! - A `View` type, which holds your `impl ViewController` and handles routing around platform -//! lifecycle methods accordingly. This is a heap allocation. -//! - A `ViewController` trait, which enables you to hook into the `NSViewController` lifecycle -//! methods. -//! - A `ViewHandle` struct, which wraps a platform-provided `NSView` and enables you to configure -//! things such as appearance and layout. -//! -//! You can use it like the following: -//! -//! ``` -//! use appkit::prelude::{View, ViewController, ViewHandle}; -//! use appkit::color::rgb; -//! -//! #[derive(Default)] -//! pub struct MyView { -//! pub view: ViewHandle -//! } -//! -//! impl ViewController for MyView { -//! fn did_load(&mut self, view: ViewHandle) { -//! self.view = view; -//! self.view.set_background_color(rgb(0, 0, 0)); -//! } -//! } -//! ``` -//! -//! For more information and examples, consult the sample code distributed in the git repository. +//! - `WKWebView` +//! - `WKUIDelegate` +//! - `WKScriptMessageHandler` +//! - `NSViewController` use std::rc::Rc; use std::cell::RefCell; use objc_id::ShareId; use objc::runtime::Object; -use objc::{msg_send, sel, sel_impl}; +use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::id; -use crate::color::Color; -use crate::constants::VIEW_CONTROLLER_PTR; -use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; -use crate::pasteboard::PasteboardType; +use crate::foundation::{id, nil, YES, NO, NSString}; +use crate::constants::WEBVIEW_CONTROLLER_PTR; +use crate::webview::controller::register_controller_class; -mod class; -mod controller; -use controller::register_controller_class; +pub mod actions; + +pub(crate) mod controller; +//pub(crate) mod process_pool; pub mod traits; -pub use traits::ViewController; +pub use traits::WebViewController; -pub mod handle; -pub use handle::ViewHandle; +pub mod config; +pub use config::WebViewConfig; /// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards /// calls into your supplied `ViewController` trait object. This involves heap allocation, but all /// of Cocoa is essentially Heap'd, so... well, enjoy. #[derive(Clone)] -pub struct View { +pub struct WebView { internal_callback_ptr: *const RefCell, - pub objc_controller: ViewHandle, + pub objc_controller: WebViewHandle, pub controller: Rc> } -impl View where T: ViewController + 'static { +impl WebView where T: WebViewController + 'static { /// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over /// to your supplied view controller. pub fn new(controller: T) -> Self { + let config = controller.config(); let controller = Rc::new(RefCell::new(controller)); let internal_callback_ptr = { @@ -72,24 +50,26 @@ impl View where T: ViewController + 'static { Rc::into_raw(cloned) }; - let inner = unsafe { + let handle = WebViewHandle::new(unsafe { let view_controller: id = msg_send![register_controller_class::(), new]; - (&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize); + (&mut *view_controller).set_ivar(WEBVIEW_CONTROLLER_PTR, internal_callback_ptr as usize); - let view: id = msg_send![view_controller, view]; - (&mut *view).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize); + // WKWebView isn't really great to subclass, so we don't bother here unlike other + // widgets in this framework. Just set and forget. + let frame: CGRect = Rect::zero().into(); + let alloc: id = msg_send![class!(WKWebView), alloc]; + let view: id = msg_send![alloc, initWithFrame:frame configuration:&*config.0]; + let _: () = msg_send![&*view_controller, setView:view]; ShareId::from_ptr(view_controller) - }; - - let handle = ViewHandle::new(inner); + }); { let mut vc = controller.borrow_mut(); (*vc).did_load(handle.clone()); } - View { + WebView { internal_callback_ptr: internal_callback_ptr, objc_controller: handle, controller: controller diff --git a/appkit/src/webview/process_pool.rs b/appkit/webview/process_pool.rs similarity index 100% rename from appkit/src/webview/process_pool.rs rename to appkit/webview/process_pool.rs diff --git a/appkit/src/webview/traits.rs b/appkit/webview/traits.rs similarity index 68% rename from appkit/src/webview/traits.rs rename to appkit/webview/traits.rs index 8c1c7b5..93d08a1 100644 --- a/appkit/src/webview/traits.rs +++ b/appkit/webview/traits.rs @@ -2,14 +2,40 @@ //! `WKWebView`. It allows you to do things such as handle opening a file (for uploads or //! in-browser-processing), handling navigation actions or JS message callbacks, and so on. -use crate::webview::action::{ +use crate::webview::config::WebViewConfig; +use crate::webview::enums::{ NavigationAction, NavigationPolicy, NavigationResponse, NavigationResponsePolicy, OpenPanelParameters }; +use crate::webview::handle::WebViewHandle; /// You can implement this on structs to handle callbacks from the underlying `WKWebView`. pub trait WebViewController { + /// Due to a quirk in how the underlying `WKWebView` works, the configuration object must be + /// set up before initializing anything. To enable this, you can implement this method and + /// return whatever `WebViewConfig` object you want. + /// + /// By default, this returns `WebViewConfig::default()`. + fn configure(&mut self) -> WebViewConfig { WebViewConfig::default() } + + /// Called when the View is ready to work with. You're passed a `ViewHandle` - this is safe to + /// store and use repeatedly, but it's not thread safe - any UI calls must be made from the + /// main thread! + fn did_load(&mut self, _view: WebViewHandle) {} + + /// Called when this is about to be added to the view heirarchy. + fn will_appear(&self) {} + + /// Called after this has been added to the view heirarchy. + fn did_appear(&self) {} + + /// Called when this is about to be removed from the view heirarchy. + fn will_disappear(&self) {} + + /// Called when this has been removed from the view heirarchy. + fn did_disappear(&self) {} + /// Called when a JS message is passed by the browser process. For instance, if you added /// `notify` as a callback, and in the browser you called /// `webkit.messageHandlers.notify.postMessage({...})` it would wind up here, with `name` being @@ -41,7 +67,7 @@ pub trait WebViewController { /// and thread the callbacks accordingly. /// /// Note that this specific callback is only - /// automatically fired if you're linking in to the `enable_webview_downloads` feature, which + /// automatically fired if you're linking in to the `webview_downloading` feature, which /// is not guaranteed to be App Store compatible. If you want a version that can go in the App /// Store, you'll likely need to write some JS in the webview to handle triggering /// downloading/saving. This is due to Apple not allowing the private methods on `WKWebView` to diff --git a/appkit/window/class.rs b/appkit/window/class.rs new file mode 100644 index 0000000..2782895 --- /dev/null +++ b/appkit/window/class.rs @@ -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(this: &Object, _: Sel, _: id) { + let window = load::(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() -> *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::(WINDOW_DELEGATE_PTR); + + // Subclassed methods + + // NSWindowDelegate methods + decl.add_method(sel!(windowWillClose:), will_close:: as extern fn(&Object, _, _)); + + DELEGATE_CLASS = decl.register(); + }); + + unsafe { + DELEGATE_CLASS + } +} diff --git a/appkit/window/config.rs b/appkit/window/config.rs new file mode 100644 index 0000000..1ea6c61 --- /dev/null +++ b/appkit/window/config.rs @@ -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; + } +} diff --git a/appkit/window/controller/class.rs b/appkit/window/controller/class.rs new file mode 100644 index 0000000..81e0479 --- /dev/null +++ b/appkit/window/controller/class.rs @@ -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() -> *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::(WINDOW_DELEGATE_PTR); + DELEGATE_CLASS = decl.register(); + }); + + unsafe { + DELEGATE_CLASS + } +} diff --git a/appkit/window/controller/mod.rs b/appkit/window/controller/mod.rs new file mode 100644 index 0000000..412cc8a --- /dev/null +++ b/appkit/window/controller/mod.rs @@ -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 { + pub objc: ShareId, + pub window: Window +} + +impl WindowController 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::(); + 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(&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 Drop for Window { + /// 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); + } + } +}*/ diff --git a/appkit/window/enums.rs b/appkit/window/enums.rs new file mode 100644 index 0000000..e25f688 --- /dev/null +++ b/appkit/window/enums.rs @@ -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 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 for usize { + fn from(visibility: WindowTitleVisibility) -> usize { + match visibility { + WindowTitleVisibility::Visible => 0, + WindowTitleVisibility::Hidden => 1 + } + } +} diff --git a/appkit/window/mod.rs b/appkit/window/mod.rs new file mode 100644 index 0000000..50885a7 --- /dev/null +++ b/appkit/window/mod.rs @@ -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 { + /// A pointer to the Objective-C `NSWindow`. Used in callback orchestration. + pub(crate) internal_callback_ptr: Option<*const RefCell>, + + /// Represents an `NSWindow` in the Objective-C runtime. + pub objc: ShareId, + + /// A delegate for this window. + pub delegate: Option>> +} + +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 Window { + /// 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>(&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(&self, toolbar: &Toolbar) { + 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(&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 Window 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::(), 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 Drop for Window { + /// 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); + } + } +}*/ diff --git a/appkit/src/window/traits.rs b/appkit/window/traits.rs similarity index 85% rename from appkit/src/window/traits.rs rename to appkit/window/traits.rs index 499245a..e0a5e1c 100644 --- a/appkit/src/window/traits.rs +++ b/appkit/window/traits.rs @@ -2,22 +2,17 @@ //! module. There's a few different ones, and it's just... cleaner, if //! it's organized here. -use crate::window::handle::WindowHandle; -use crate::window::WindowConfig; +use crate::window::Window; /// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa /// lifecycle methods, but mix in a few extra things to handle offering configuration tools /// in lieu of subclasses. -pub trait WindowController { - /// The framework offers a standard, modern `NSWindow` by default - but sometimes you want - /// something else. Implement this and return your desired Window configuration. - fn config(&self) -> WindowConfig { WindowConfig::default() } - +pub trait WindowDelegate { /// Fires when this window has loaded in memory, and is about to display. This is a good point /// to set up your views and what not. /// /// If you're coming from the web, you can think of this as `DOMContentLoaded`. - fn did_load(&mut self, _window: WindowHandle) {} + fn did_load(&mut self, _window: Window) {} /// Fires when a window is going to close. You might opt to, say, clean up things here - /// perhaps you have a long running task, or something that should be removed. diff --git a/derives/Cargo.toml b/derives/Cargo.toml deleted file mode 100644 index 3b1430e..0000000 --- a/derives/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "appkit-derive" -description = "A crate containing macros for appkit." -version = "0.1.0" -authors = ["Ryan McGrath "] -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" diff --git a/derives/README.md b/derives/README.md deleted file mode 100644 index 8b9e0a9..0000000 --- a/derives/README.md +++ /dev/null @@ -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/). diff --git a/derives/src/lib.rs b/derives/src/lib.rs deleted file mode 100644 index 3c7e9de..0000000 --- a/derives/src/lib.rs +++ /dev/null @@ -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> { - self.view.get_handle() - } - } - }; - - TokenStream::from(expanded) -} diff --git a/examples/window-controller/Cargo.toml b/examples/window-controller/Cargo.toml new file mode 100644 index 0000000..5c64be0 --- /dev/null +++ b/examples/window-controller/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "window-controller" +version = "0.1.0" +authors = ["Ryan McGrath "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +appkit = { path = "../../appkit" } diff --git a/examples/window-controller/src/main.rs b/examples/window-controller/src/main.rs new file mode 100644 index 0000000..6a372bd --- /dev/null +++ b/examples/window-controller/src/main.rs @@ -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 +} + +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(); +} diff --git a/examples/window-delegate/Cargo.toml b/examples/window-delegate/Cargo.toml new file mode 100644 index 0000000..0850e61 --- /dev/null +++ b/examples/window-delegate/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "window-delegate" +version = "0.1.0" +authors = ["Ryan McGrath "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +appkit = { path = "../../appkit" } diff --git a/examples/window-delegate/src/main.rs b/examples/window-delegate/src/main.rs new file mode 100644 index 0000000..75e57e0 --- /dev/null +++ b/examples/window-delegate/src/main.rs @@ -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 +} + +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(); +} diff --git a/examples/window/Cargo.toml b/examples/window/Cargo.toml new file mode 100644 index 0000000..84864fd --- /dev/null +++ b/examples/window/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "window" +version = "0.1.0" +authors = ["Ryan McGrath "] +edition = "2018" + +[dependencies] +appkit = { path = "../../appkit" } diff --git a/examples/window/src/main.rs b/examples/window/src/main.rs new file mode 100644 index 0000000..5d36ca7 --- /dev/null +++ b/examples/window/src/main.rs @@ -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(); +}