From e4f96b4ab5657ebde5b913fdbe1991a331e275cf Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 20 Mar 2020 18:37:00 -0700 Subject: [PATCH] Working on getting WKWebView working again - close --- appkit/lib.rs | 6 +- appkit/networking/mod.rs | 1 + appkit/view/mod.rs | 27 ++- appkit/webview/actions.rs | 2 +- appkit/webview/{controller.rs => class.rs} | 103 +++------ appkit/webview/config.rs | 10 +- appkit/webview/enums.rs | 31 +-- appkit/webview/handle.rs | 69 ------ appkit/webview/mod.rs | 253 ++++++++++++++------- appkit/webview/traits.rs | 21 +- 10 files changed, 259 insertions(+), 264 deletions(-) rename appkit/webview/{controller.rs => class.rs} (61%) delete mode 100644 appkit/webview/handle.rs diff --git a/appkit/lib.rs b/appkit/lib.rs index c4d2806..0d1839c 100644 --- a/appkit/lib.rs +++ b/appkit/lib.rs @@ -69,13 +69,13 @@ pub mod prelude { pub use crate::networking::URLRequest; pub use crate::window::{ - Window, /*WindowController,*/ WindowDelegate + Window, WindowConfig, WindowDelegate }; #[cfg(feature = "webview")] pub use crate::webview::{ - WebView, WebViewConfig, WebViewController + WebView, WebViewConfig, WebViewDelegate }; - //pub use crate::view::{View, ViewController, ViewDelegate}; + pub use crate::view::{View, ViewDelegate}; } diff --git a/appkit/networking/mod.rs b/appkit/networking/mod.rs index bcc3c33..d9eb13c 100644 --- a/appkit/networking/mod.rs +++ b/appkit/networking/mod.rs @@ -7,6 +7,7 @@ use objc_id::Id; use crate::foundation::{id, NSString}; +#[derive(Debug)] pub struct URLRequest { pub inner: Id } diff --git a/appkit/view/mod.rs b/appkit/view/mod.rs index 1d71085..7965973 100644 --- a/appkit/view/mod.rs +++ b/appkit/view/mod.rs @@ -9,7 +9,7 @@ use std::rc::Rc; use std::cell::RefCell; use objc_id::ShareId; -use objc::runtime::Object; +use objc::runtime::{Class, Object}; use objc::{msg_send, sel, sel_impl}; use crate::foundation::{id, YES, NO, NSArray, NSString}; @@ -28,6 +28,16 @@ pub use traits::ViewDelegate; pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr"; +/// A helper method for instantiating view classes and applying default settings to them. +fn allocate_view(registration_fn: fn() -> *const Class) -> id { + unsafe { + let view: id = msg_send![registration_fn(), new]; + let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + let _: () = msg_send![view, setWantsLayer:YES]; + view + } +} + /// 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. @@ -77,12 +87,7 @@ impl Default for View { 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]; - let _: () = msg_send![view, setWantsLayer:YES]; - view - }; + let view = allocate_view(register_view_class); View { internal_callback_ptr: None, @@ -111,11 +116,11 @@ impl View where T: ViewDelegate + 'static { Rc::into_raw(cloned) }; - let view = unsafe { - let view: id = msg_send![register_view_class_with_delegate::(), new]; - let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + let view = allocate_view(register_view_class_with_delegate::); + 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 mut view = View { diff --git a/appkit/webview/actions.rs b/appkit/webview/actions.rs index 65af724..59ce7e7 100644 --- a/appkit/webview/actions.rs +++ b/appkit/webview/actions.rs @@ -4,6 +4,7 @@ use objc::{msg_send, sel, sel_impl}; use crate::foundation::{id, BOOL, YES, NO, NSInteger}; use crate::networking::URLRequest; +use crate::webview::enums::NavigationType; #[derive(Debug)] pub struct NavigationAction { @@ -42,7 +43,6 @@ impl NavigationResponse { } } - #[derive(Copy, Clone, Debug, Default)] pub struct OpenPanelParameters { pub allows_directories: bool, diff --git a/appkit/webview/controller.rs b/appkit/webview/class.rs similarity index 61% rename from appkit/webview/controller.rs rename to appkit/webview/class.rs index 2ef0e60..d17ca3b 100644 --- a/appkit/webview/controller.rs +++ b/appkit/webview/class.rs @@ -12,51 +12,13 @@ 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; - -/// Loads and configures ye old WKWebView/View for this controller. -extern fn load_view(this: &mut Object, _: Sel) { - unsafe { - let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR); - - // Technically private! - #[cfg(feature = "webview-downloading")] - let process_pool: id = msg_send![configuration, processPool]; - #[cfg(feature = "webview-downloading")] - let _: () = msg_send![process_pool, _setDownloadDelegate:&*this]; - - 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]; - let _: () = msg_send![webview, setTranslatesAutoresizingMaskIntoConstraints:NO]; - - // Provide an easy way to grab this later - (*this).set_ivar(WEBVIEW_VAR, webview); - - // Clean this up to be safe, as WKWebView makes a copy and we don't need it anymore. - (*this).set_ivar(WEBVIEW_CONFIG_VAR, nil); - - let _: () = msg_send![this, setView:webview]; - } -} - -/// Used to connect delegates - doing this in `loadView` can be... bug-inducing. -extern fn view_did_load(this: &Object, _: Sel) { - unsafe { - let webview: id = *this.get_ivar(WEBVIEW_VAR); - let _: () = msg_send![webview, setNavigationDelegate:&*this]; - let _: () = msg_send![webview, setUIDelegate:&*this]; - } -} +use crate::webview::{WEBVIEW_DELEGATE_PTR, WebViewDelegate}; +use crate::webview::actions::{NavigationAction, NavigationResponse, OpenPanelParameters}; +use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy}; /// 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) { +extern fn alert(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) { let alert = NSString::wrap(s).to_str(); println!("Alert: {}", alert); @@ -68,7 +30,7 @@ extern fn alert(_: &Object, _: Sel, _: id, s: id } /*unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); + let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let webview = ptr as *const T; (*webview).alert(alert); }*/ @@ -81,21 +43,21 @@ 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) { +extern fn on_message(this: &Object, _: Sel, _: id, script_message: id) { unsafe { 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 ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let webview = ptr as *const T; (*webview).on_message(name, body); } } /// Fires when deciding a navigation policy - i.e, should something be allowed or not. -extern fn decide_policy_for_action(this: &Object, _: Sel, _: id, action: id, handler: usize) { +extern fn decide_policy_for_action(this: &Object, _: Sel, _: id, action: id, handler: usize) { let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); + let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let webview = ptr as *const T; &*webview }; @@ -111,9 +73,9 @@ extern fn decide_policy_for_action(this: &Object } /// Fires when deciding a navigation policy - i.e, should something be allowed or not. -extern fn decide_policy_for_response(this: &Object, _: Sel, _: id, response: id, handler: usize) { +extern fn decide_policy_for_response(this: &Object, _: Sel, _: id, response: id, handler: usize) { let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); + let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let webview = ptr as *const T; &*webview }; @@ -126,9 +88,9 @@ extern fn decide_policy_for_response(this: &Obje } /// Fires when deciding a navigation policy - i.e, should something be allowed or not. -extern fn run_open_panel(this: &Object, _: Sel, _: id, params: id, _: id, handler: usize) { +extern fn run_open_panel(this: &Object, _: Sel, _: id, params: id, _: id, handler: usize) { let webview = unsafe { - let ptr: usize = *this.get_ivar(WEBVIEW_CONTROLLER_PTR); + let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let webview = ptr as *const T; &*webview }; @@ -139,7 +101,7 @@ extern fn run_open_panel(this: &Object, _: Sel, match urls { Some(u) => { let nsurls: NSArray = u.iter().map(|s| { - let s = NSString::new(&s); + let s = NSString::new(s); msg_send![class!(NSURL), URLWithString:s] }).collect::>().into(); @@ -155,9 +117,9 @@ extern fn run_open_panel(this: &Object, _: Sel, /// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private /// API. #[cfg(feature = "webview-downloading")] -extern fn handle_download(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) { +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); + let ptr: usize = *this.get_ivar(WEBVIEW_DELEGATE_PTR); let webview = ptr as *const T; &*webview }; @@ -179,27 +141,34 @@ extern fn handle_download(this: &Object, _: Sel, }); } - /// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as /// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various /// varieties of delegates needed there). -pub fn register_controller_class< - T: ViewController + WebViewController + 'static, ->() -> *const Class { +pub fn register_webview_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("NSViewController").unwrap(); - let mut decl = ClassDecl::new("RSTWebViewController", superclass).unwrap(); + let superclass = class!(WKWebView); + let decl = ClassDecl::new("RSTWebView", superclass).unwrap(); + VIEW_CLASS = decl.register(); + }); - decl.add_ivar::(WEBVIEW_CONFIG_VAR); - decl.add_ivar::(WEBVIEW_VAR); - decl.add_ivar::(WEBVIEW_CONTROLLER_PTR); + unsafe { VIEW_CLASS } +} - // NSViewController - decl.add_method(sel!(loadView), load_view:: as extern fn(&mut Object, _)); - decl.add_method(sel!(viewDidLoad), view_did_load:: as extern fn(&Object, _)); +/// Registers an `NSViewController` that we effectively turn into a `WebViewController`. Acts as +/// both a subclass of `NSViewController` and a delegate of the held `WKWebView` (for the various +/// varieties of delegates needed there). +pub fn register_webview_delegate_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!(NSObject); + let mut decl = ClassDecl::new("RSTWebViewDelegate", superclass).unwrap(); + + decl.add_ivar::(WEBVIEW_DELEGATE_PTR); // WKNavigationDelegate decl.add_method(sel!(webView:decidePolicyForNavigationAction:decisionHandler:), decide_policy_for_action:: as extern fn(&Object, _, _, id, usize)); @@ -214,7 +183,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. + // store, so... screw it, feature-gate it. #[cfg(feature = "webview-downloading")] decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download:: as extern fn(&Object, _, id, id, usize)); diff --git a/appkit/webview/config.rs b/appkit/webview/config.rs index 420d85d..731b8f9 100644 --- a/appkit/webview/config.rs +++ b/appkit/webview/config.rs @@ -5,7 +5,7 @@ use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, YES, NSString}; +use crate::foundation::{id, YES, NO, NSString}; use crate::webview::enums::InjectAt; /// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime @@ -63,4 +63,12 @@ impl WebViewConfig { let _: () = msg_send![preferences, setValue:yes forKey:key]; } } + + pub(crate) fn attach_handlers(&self, target: id) { + + } + + pub fn into_inner(self) -> id { + &mut *self.objc + } } diff --git a/appkit/webview/enums.rs b/appkit/webview/enums.rs index f0427ea..92f4200 100644 --- a/appkit/webview/enums.rs +++ b/appkit/webview/enums.rs @@ -3,6 +3,7 @@ use crate::foundation::NSInteger; /// Describes a navigation type from within the `WebView`. +#[derive(Clone, Copy, Debug)] pub enum NavigationType { /// A user activated a link. LinkActivated, @@ -23,21 +24,21 @@ pub enum NavigationType { 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); } +impl From for NSInteger { + fn from(nav_type: NavigationType) -> Self { + match nav_type { + NavigationType::Other => -1, + NavigationType::LinkActivated => 0, + NavigationType::FormSubmitted => 1, + NavigationType::BackForward => 2, + NavigationType::Reload => 3, + NavigationType::FormResubmitted => 4 } } } /// Describes the policy for a given navigation. +#[derive(Clone, Copy, Debug)] pub enum NavigationPolicy { /// Should be canceled. Cancel, @@ -47,8 +48,8 @@ pub enum NavigationPolicy { } impl From for NSInteger { - fn into(self) -> Self { - match self { + fn from(policy: NavigationPolicy) -> Self { + match policy { NavigationPolicy::Cancel => 0, NavigationPolicy::Allow => 1 } @@ -56,6 +57,7 @@ impl From for NSInteger { } /// Describes a response policy for a given navigation. +#[derive(Clone, Copy, Debug)] pub enum NavigationResponsePolicy { /// Should be canceled. Cancel, @@ -70,8 +72,8 @@ pub enum NavigationResponsePolicy { } impl From for NSInteger { - fn into(self) -> Self { - match self { + fn from(policy: NavigationResponsePolicy) -> Self { + match policy { NavigationResponsePolicy::Cancel => 0, NavigationResponsePolicy::Allow => 1, @@ -82,6 +84,7 @@ impl From for NSInteger { } /// Dictates where a given user script should be injected. +#[derive(Clone, Copy, Debug)] pub enum InjectAt { /// Inject at the start of the document. Start = 0, diff --git a/appkit/webview/handle.rs b/appkit/webview/handle.rs deleted file mode 100644 index e2a889a..0000000 --- a/appkit/webview/handle.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! 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 -//! AutoLayout - for more information, see the AutoLayout tutorial. - -use objc_id::ShareId; -use objc::runtime::Object; -use objc::{msg_send, sel, sel_impl}; - -use crate::foundation::{id, YES, NSArray, NSString}; -use crate::color::Color; -use crate::constants::BACKGROUND_COLOR; -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 `WKWebView` for easier recordkeeping, since it'll need to hold the `WKWebView` on that -/// side anyway. -#[derive(Debug, Default, Clone)] -pub struct WebViewHandle { - /// A pointer to the Objective-C runtime view controller. - pub objc: 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 WebViewHandle { - pub(crate) fn new(object: ShareId) -> Self { - let view: id = unsafe { - msg_send![&*object, view] - }; - - WebViewHandle { - objc: Some(object), - 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] }), - } - } -} diff --git a/appkit/webview/mod.rs b/appkit/webview/mod.rs index 9b3e068..7a29d88 100644 --- a/appkit/webview/mod.rs +++ b/appkit/webview/mod.rs @@ -4,133 +4,222 @@ //! - `WKWebView` //! - `WKUIDelegate` //! - `WKScriptMessageHandler` -//! - `NSViewController` use std::rc::Rc; use std::cell::RefCell; use objc_id::ShareId; -use objc::runtime::Object; +use objc::runtime::{Class, Object}; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, nil, YES, NO, NSString}; -use crate::constants::WEBVIEW_CONTROLLER_PTR; -use crate::webview::controller::register_controller_class; +use crate::foundation::{id, nil, YES, NO, CGRect, NSString}; +use crate::geometry::Rect; +use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; pub mod actions; +pub mod enums; -pub(crate) mod controller; +pub(crate) mod class; +use class::{register_webview_class, register_webview_class_with_delegate}; //pub(crate) mod process_pool; pub mod traits; -pub use traits::WebViewController; +pub use traits::WebViewDelegate; 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 WebView { - internal_callback_ptr: *const RefCell, - pub objc_controller: WebViewHandle, - pub controller: Rc> +pub(crate) static WEBVIEW_DELEGATE_PTR: &str = "rstWebViewDelegatePtr"; + +fn allocate_webview( + config: WebViewConfig, + delegate: Option<&Object> +) -> id { + unsafe { + let configuration = config.into_inner(); + + if let Some(delegate) = objc_delegate { + // Technically private! + #[cfg(feature = "webview-downloading")] + let process_pool: id = msg_send![configuration, processPool]; + #[cfg(feature = "webview-downloading")] + let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate]; + + // add handlers + for + } + + let zero: CGRect = Rect::zero().into(); + let webview_alloc: id = msg_send![register_webview_class(), alloc]; + let webview: id = msg_send![webview_alloc, initWithFrame:zero configuration:configuration]; + + let _: () = msg_send![webview, setWantsLayer:YES]; + let _: () = msg_send![webview, setTranslatesAutoresizingMaskIntoConstraints:NO]; + let _: () = msg_send![webview, setNavigationDelegate:webview]; + let _: () = msg_send![webview, setUIDelegate:webview]; + + webview + } } -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)); +pub struct WebView { + /// A pointer to the Objective-C runtime view controller. + pub objc: ShareId, + + /// We need to store the underlying delegate separately from the `WKWebView` - this is a where + /// we do so. + pub objc_delegate: Option>, + + /// An internal callback pointer that we use in delegate loopbacks. Default implementations + /// don't require this. + pub(crate) 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 WebView { + fn default() -> Self { + WebView::new(WebViewConfig::default()) + } +} + +impl WebView { + pub fn new(config: WebViewConfig) -> Self { + let view = allocate_webview(register_webview_class, config, None); + + WebView { + internal_callback_ptr: None, + delegate: None, + objc_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: unsafe { ShareId::from_ptr(view) }, + } + } +} + +impl WebView where T: WebViewDelegate + 'static { + /// Initializes a new WebView with a given `WebViewDelegate`. This enables you to respond to events + /// and customize the view as a module, similar to class-based systems. + pub fn with(config: WebViewConfig, delegate: T) -> WebView { + let delegate = Rc::new(RefCell::new(delegate)); let internal_callback_ptr = { - let cloned = Rc::clone(&controller); + let cloned = Rc::clone(&delegate); Rc::into_raw(cloned) }; - let handle = WebViewHandle::new(unsafe { - let view_controller: id = msg_send![register_controller_class::(), new]; - (&mut *view_controller).set_ivar(WEBVIEW_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 objc_delegate = unsafe { + let objc_delegate: id = msg_send![register_webview_delegate_class::, new]; + (&mut *objc_delegate).set_ivar(WEBVIEW_DELEGATE_PTR, internal_callback_ptr as usize); + ShareId::from_ptr(objc_delegate) + }; + + let view = allocate_webview(config, Some(&objc_delegate)); + + let mut view = WebView { + internal_callback_ptr: Some(internal_callback_ptr), + delegate: None, + objc_delegate: Some(objc_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: unsafe { ShareId::from_ptr(view) }, + }; { - let mut vc = controller.borrow_mut(); - (*vc).did_load(handle.clone()); + let mut delegate = delegate.borrow_mut(); + (*delegate).did_load(view.clone_as_handle()); } - WebView { - internal_callback_ptr: internal_callback_ptr, - objc_controller: handle, - controller: controller - } - } - - pub fn set_background_color(&self, color: Color) { - self.objc_controller.set_background_color(color); - } - - pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { - self.objc_controller.register_for_dragged_types(types); - } - - pub fn top(&self) -> &LayoutAnchorY { - &self.objc_controller.top - } - - pub fn leading(&self) -> &LayoutAnchorX { - &self.objc_controller.leading - } - - pub fn trailing(&self) -> &LayoutAnchorX { - &self.objc_controller.trailing - } - - pub fn bottom(&self) -> &LayoutAnchorY { - &self.objc_controller.bottom - } - - pub fn width(&self) -> &LayoutAnchorDimension { - &self.objc_controller.width - } - - pub fn height(&self) -> &LayoutAnchorDimension { - &self.objc_controller.height + view.delegate = Some(delegate); + view } } -impl Layout for View { +impl WebView { + /// An internal method that returns a clone of this object, sans references to the delegate or + /// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to + /// reference, customize and use the view but without the trickery of holding pieces of the + /// delegate - the `View` is the only true holder of those. + pub(crate) fn clone_as_handle(&self) -> WebView { + WebView { + internal_callback_ptr: None, + delegate: None, + top: self.top.clone(), + leading: self.leading.clone(), + trailing: self.trailing.clone(), + bottom: self.bottom.clone(), + width: self.width.clone(), + height: self.height.clone(), + center_x: self.center_x.clone(), + center_y: self.center_y.clone(), + objc: self.objc.clone(), + objc_delegate: None + } + } +} + +impl Layout for WebView { /// Returns the Objective-C object used for handling the view heirarchy. - fn get_backing_node(&self) -> Option> { - self.objc_controller.objc.clone() + fn get_backing_node(&self) -> ShareId { + self.objc.clone() } fn add_subview(&self, subview: &V) { - self.objc_controller.add_subview(subview); + } } -impl std::fmt::Debug for View { +impl std::fmt::Debug for WebView { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "View ({:p})", self) + write!(f, "WebView ({:p})", self) } } -impl Drop for View { +impl Drop for WebView { /// A bit of extra cleanup for delegate callback pointers. fn drop(&mut self) { unsafe { - let _ = Rc::from_raw(self.internal_callback_ptr); + if let Some(ptr) = self.internal_callback_ptr { + let _ = Rc::from_raw(ptr); + } } } } diff --git a/appkit/webview/traits.rs b/appkit/webview/traits.rs index 93d08a1..4bd614a 100644 --- a/appkit/webview/traits.rs +++ b/appkit/webview/traits.rs @@ -2,27 +2,16 @@ //! `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::config::WebViewConfig; -use crate::webview::enums::{ - NavigationAction, NavigationPolicy, - NavigationResponse, NavigationResponsePolicy, - OpenPanelParameters -}; -use crate::webview::handle::WebViewHandle; +use crate::webview::WebView; +use crate::webview::actions::{NavigationAction, NavigationResponse, OpenPanelParameters}; +use crate::webview::enums::{NavigationPolicy, NavigationResponsePolicy}; /// 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() } - +pub trait WebViewDelegate { /// 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) {} + fn did_load(&mut self, _webview: WebView) {} /// Called when this is about to be added to the view heirarchy. fn will_appear(&self) {}