diff --git a/crates/icrate/Cargo.toml b/crates/icrate/Cargo.toml index f263bc62a..e6b867d67 100644 --- a/crates/icrate/Cargo.toml +++ b/crates/icrate/Cargo.toml @@ -48,37 +48,31 @@ targets = [ [[example]] name = "basic_usage" required-features = [ - "Foundation", - "Foundation_NSArray", - "Foundation_NSDictionary", - "Foundation_NSEnumerator", + "unstable-example-basic_usage" ] [[example]] name = "delegate" required-features = [ - "apple", - "Foundation", - "Foundation_NSString", - "AppKit_NSResponder", + "unstable-example-delegate" ] [[example]] name = "speech_synthesis" required-features = [ - "apple", - "Foundation", - "Foundation_NSString", + "unstable-example-speech_synthesis" ] [[example]] name = "nspasteboard" required-features = [ - "apple", - "Foundation", - "Foundation_NSArray", - "Foundation_NSDictionary", - "Foundation_NSString", + "unstable-example-nspasteboard" +] + +[[example]] +name = "browser" +required-features = [ + "unstable-example-browser" ] [features] @@ -117,6 +111,54 @@ unstable-private = [] # https://github.com/madsmtm/objc2/issues/new unstable-static-nsstring = [] +# Examples + +unstable-example-basic_usage = [ + "Foundation", + "Foundation_NSArray", + "Foundation_NSDictionary", + "Foundation_NSEnumerator", +] +unstable-example-delegate = [ + "apple", + "Foundation", + "Foundation_NSString", + "AppKit", + "AppKit_NSResponder", +] +unstable-example-speech_synthesis = [ + "apple", + "Foundation", + "Foundation_NSString", +] +unstable-example-nspasteboard = [ + "apple", + "Foundation", + "Foundation_NSArray", + "Foundation_NSDictionary", + "Foundation_NSString", +] +unstable-example-browser = [ + "apple", + "AppKit", + "AppKit_NSButton", + "AppKit_NSColor", + "AppKit_NSMenu", + "AppKit_NSMenuItem", + "AppKit_NSStackView", + "AppKit_NSTextAttachmentCell", + "AppKit_NSTextField", + "AppKit_NSTextView", + "AppKit_NSWindow", + "Foundation", + "Foundation_NSString", + "Foundation_NSURL", + "Foundation_NSURLRequest", + "WebKit", + "WebKit_WKNavigation", + "WebKit_WKWebView", +] + # Frameworks Accessibility = ["Foundation"] @@ -213,14 +255,14 @@ WebKit = [ ] # Helps with CI -unstable-frameworks-all = ["unstable-frameworks-ios", "unstable-frameworks-macos-13"] +unstable-frameworks-all = ["unstable-frameworks-ios", "unstable-frameworks-macos-13", "unstable-example-basic_usage"] unstable-frameworks-gnustep = ["AppKit_all", "Foundation_all"] unstable-frameworks-gnustep-32bit = ["Foundation_all"] -unstable-frameworks-ios = ["Accessibility_all", "AdServices_all", "AdSupport_all", "AuthenticationServices_all", "AutomaticAssessmentConfiguration_all", "BackgroundAssets_all", "BackgroundTasks_all", "BusinessChat_all", "CallKit_all", "ClassKit_all", "CloudKit_all", "Contacts_all", "CoreData_all", "CoreAnimation_all", "CoreLocation_all", "DataDetection_all", "DeviceCheck_all", "EventKit_all", "ExtensionKit_all", "ExternalAccessory_all", "FileProvider_all", "FileProviderUI_all", "Foundation_all", "GameController_all", "GameKit_all", "IdentityLookup_all", "LocalAuthentication_all", "LinkPresentation_all", "MapKit_all", "Metal_all", "MetalKit_all", "SoundAnalysis_all", "Speech_all", "StoreKit_all", "UniformTypeIdentifiers_all", "UserNotifications_all", "WebKit_all"] -unstable-frameworks-macos-10-7 = ["Accessibility_all", "AppKit_all", "Automator_all", "CoreAnimation_all", "CoreData_all", "CoreLocation_all", "ExceptionHandling_all", "Foundation_all", "InputMethodKit_all", "OSAKit_all", "StoreKit_all", "WebKit_all"] +unstable-frameworks-ios = ["Accessibility_all", "AdServices_all", "AdSupport_all", "AuthenticationServices_all", "AutomaticAssessmentConfiguration_all", "BackgroundAssets_all", "BackgroundTasks_all", "BusinessChat_all", "CallKit_all", "ClassKit_all", "CloudKit_all", "Contacts_all", "CoreData_all", "CoreAnimation_all", "CoreLocation_all", "DataDetection_all", "DeviceCheck_all", "EventKit_all", "ExtensionKit_all", "ExternalAccessory_all", "FileProvider_all", "FileProviderUI_all", "Foundation_all", "GameController_all", "GameKit_all", "IdentityLookup_all", "LocalAuthentication_all", "LinkPresentation_all", "MapKit_all", "Metal_all", "MetalKit_all", "SoundAnalysis_all", "Speech_all", "StoreKit_all", "UniformTypeIdentifiers_all", "UserNotifications_all", "WebKit_all", "unstable-example-speech_synthesis"] +unstable-frameworks-macos-10-7 = ["Accessibility_all", "AppKit_all", "Automator_all", "CoreAnimation_all", "CoreData_all", "CoreLocation_all", "ExceptionHandling_all", "Foundation_all", "InputMethodKit_all", "OSAKit_all", "StoreKit_all", "unstable-example-delegate", "unstable-example-nspasteboard", "unstable-example-speech_synthesis"] unstable-frameworks-macos-10-13 = ["unstable-frameworks-macos-10-7", "BusinessChat_all", "CloudKit_all", "Contacts_all", "EventKit_all", "ExternalAccessory_all", "GameController_all", "GameKit_all", "LocalAuthentication_all", "LocalAuthenticationEmbeddedUI_all", "MapKit_all", "MetalKit_all"] unstable-frameworks-macos-11 = ["unstable-frameworks-macos-10-13", "AdServices_all", "AdSupport_all", "AuthenticationServices_all", "AutomaticAssessmentConfiguration_all", "ClassKit_all", "DeviceCheck_all", "FileProvider_all", "FileProviderUI_all", "LinkPresentation_all", "Metal_all", "SoundAnalysis_all", "Speech_all", "UniformTypeIdentifiers_all", "UserNotifications_all"] -unstable-frameworks-macos-12 = ["unstable-frameworks-macos-11", "DataDetection_all", "LocalAuthenticationEmbeddedUI_all", "MailKit_all"] +unstable-frameworks-macos-12 = ["unstable-frameworks-macos-11", "DataDetection_all", "LocalAuthenticationEmbeddedUI_all", "MailKit_all", "WebKit_all", "unstable-example-browser"] unstable-frameworks-macos-13 = ["unstable-frameworks-macos-12", "BackgroundAssets_all", "BackgroundTasks_all", "CallKit_all", "ExtensionKit_all", "IdentityLookup_all", "MetalFX_all"] # Temporary fixes until we can autogenerate these diff --git a/crates/icrate/examples/README.md b/crates/icrate/examples/README.md new file mode 100644 index 000000000..6489b867a --- /dev/null +++ b/crates/icrate/examples/README.md @@ -0,0 +1,8 @@ +#### Running the examples + +The examples can be run as follows: + +```sh +cargo run --package=icrate --example=basic_usage --features=unstable-example-basic_usage +cargo run --package=icrate --example=delegate --features=unstable-example-delegate +``` diff --git a/crates/icrate/examples/browser.rs b/crates/icrate/examples/browser.rs new file mode 100644 index 000000000..834a49ad8 --- /dev/null +++ b/crates/icrate/examples/browser.rs @@ -0,0 +1,286 @@ +#![deny(unsafe_op_in_unsafe_fn)] + +use icrate::{ + ns_string, + objc2::{ + declare::{Ivar, IvarDrop}, + declare_class, extern_methods, msg_send, + rc::{Allocated, Id, Shared}, + runtime::{Object, Sel}, + sel, ClassType, ProtocolObject, + }, + AppKit::{ + NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered, + NSBezelStyleShadowlessSquare, NSButton, NSColor, NSControl, NSControlTextEditingDelegate, + NSLayoutAttributeHeight, NSLayoutAttributeWidth, NSMenu, NSMenuItem, NSStackView, + NSStackViewDistributionFill, NSStackViewDistributionFillEqually, NSTextField, + NSTextFieldDelegate, NSTextView, NSUserInterfaceLayoutOrientationHorizontal, + NSUserInterfaceLayoutOrientationVertical, NSWindow, NSWindowStyleMaskClosable, + NSWindowStyleMaskResizable, NSWindowStyleMaskTitled, + }, + Foundation::{NSObject, NSObjectProtocol, NSPoint, NSRect, NSSize, NSURLRequest, NSURL}, + WebKit::{WKNavigation, WKNavigationDelegate, WKWebView}, +}; + +declare_class!( + struct Delegate { + text_field: IvarDrop, "_text_field">, + web_view: IvarDrop, "_web_view">, + } + mod ivars; + + unsafe impl ClassType for Delegate { + type Super = NSObject; + const NAME: &'static str = "Delegate"; + } + + unsafe impl Delegate { + #[method(initWithTextField:andWebView:)] + #[allow(non_snake_case)] + unsafe fn __init_withTextField_andWebView( + self: &mut Self, + text_field: *mut NSTextField, + web_view: *mut WKWebView, + ) -> Option<&mut Self> { + let this: Option<&mut Self> = msg_send![super(self), init]; + let this = this?; + Ivar::write(&mut this.text_field, unsafe { Id::retain(text_field) }?); + Ivar::write(&mut this.web_view, unsafe { Id::retain(web_view) }?); + Some(this) + } + } + + unsafe impl NSControlTextEditingDelegate for Delegate { + #[method(control:textView:doCommandBySelector:)] + #[allow(non_snake_case)] + unsafe fn control_textView_doCommandBySelector( + &self, + _control: &NSControl, + text_view: &NSTextView, + command_selector: Sel, + ) -> bool { + if command_selector == sel!(insertNewline:) { + if let Some(url) = unsafe { NSURL::URLWithString(&text_view.string()) } { + unsafe { + self.web_view + .loadRequest(&NSURLRequest::requestWithURL(&url)) + }; + return true.into(); + } + } + false + } + } + + unsafe impl NSTextFieldDelegate for Delegate {} + + unsafe impl WKNavigationDelegate for Delegate { + #[method(webView:didFinishNavigation:)] + #[allow(non_snake_case)] + unsafe fn webView_didFinishNavigation( + &self, + web_view: &WKWebView, + _navigation: Option<&WKNavigation>, + ) { + unsafe { + if let Some(url) = web_view.URL().and_then(|url| url.absoluteString()) { + self.text_field.setStringValue(&url); + } + } + } + } +); + +extern_methods!( + unsafe impl Delegate { + #[method_id(initWithTextField:andWebView:)] + #[allow(non_snake_case)] + pub fn initWithTextField_andWebView( + this: Option>, + text_field: &NSTextField, + web_view: &WKWebView, + ) -> Id; + } +); + +unsafe impl NSObjectProtocol for Delegate {} + +fn main() { + let app = unsafe { NSApplication::sharedApplication() }; + unsafe { app.setActivationPolicy(NSApplicationActivationPolicyRegular) }; + + // create the app window + let window = { + let this = NSWindow::alloc(); + let content_rect = NSRect::new(NSPoint::new(0., 0.), NSSize::new(1024., 768.)); + let style = + NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskTitled; + let backing_store_type = NSBackingStoreBuffered; + let flag = false; + unsafe { + NSWindow::initWithContentRect_styleMask_backing_defer( + this, + content_rect, + style, + backing_store_type, + flag, + ) + } + }; + + // create the web view + let web_view = { + let this = WKWebView::alloc(); + let frame_rect = NSRect::ZERO; + unsafe { WKWebView::initWithFrame(this, frame_rect) } + }; + + // create the nav bar view + let nav_bar = { + let frame_rect = NSRect::ZERO; + let this = NSStackView::alloc(); + let this = unsafe { NSStackView::initWithFrame(this, frame_rect) }; + unsafe { + this.setOrientation(NSUserInterfaceLayoutOrientationHorizontal); + this.setAlignment(NSLayoutAttributeHeight); + this.setDistribution(NSStackViewDistributionFill); + this.setSpacing(0.); + } + this + }; + + // create the nav buttons view + let nav_buttons = { + let frame_rect = NSRect::ZERO; + let this = NSStackView::alloc(); + let this = unsafe { NSStackView::initWithFrame(this, frame_rect) }; + unsafe { + this.setOrientation(NSUserInterfaceLayoutOrientationHorizontal); + this.setAlignment(NSLayoutAttributeHeight); + this.setDistribution(NSStackViewDistributionFillEqually); + this.setSpacing(0.); + } + this + }; + + // create the back button + let back_button = { + // configure the button to navigate the webview backward + let title = ns_string!("back"); + let target = Some::<&Object>(&web_view); + let action = Some(sel!(goBack)); + let this = unsafe { NSButton::buttonWithTitle_target_action(title, target, action) }; + unsafe { this.setBezelStyle(NSBezelStyleShadowlessSquare) }; + this + }; + + // create the forward button + let forward_button = { + // configure the button to navigate the web view forward + let title = ns_string!("forward"); + let target = Some::<&Object>(&web_view); + let action = Some(sel!(goForward)); + let this = unsafe { NSButton::buttonWithTitle_target_action(title, target, action) }; + unsafe { this.setBezelStyle(NSBezelStyleShadowlessSquare) }; + this + }; + + unsafe { + nav_buttons.addArrangedSubview(&back_button); + nav_buttons.addArrangedSubview(&forward_button); + } + + // create the url text field + let nav_url = { + let frame_rect = NSRect::ZERO; + let this = NSTextField::alloc(); + let this = unsafe { NSTextField::initWithFrame(this, frame_rect) }; + unsafe { + this.setDrawsBackground(true); + this.setBackgroundColor(Some(&NSColor::lightGrayColor())); + this.setTextColor(Some(&NSColor::blackColor())); + } + this + }; + + unsafe { + nav_bar.addArrangedSubview(&nav_buttons); + nav_bar.addArrangedSubview(&nav_url); + } + + // create the window content view + let content_view = { + let frame_rect = unsafe { window.frame() }; + let this = NSStackView::alloc(); + let this = unsafe { NSStackView::initWithFrame(this, frame_rect) }; + unsafe { + this.setOrientation(NSUserInterfaceLayoutOrientationVertical); + this.setAlignment(NSLayoutAttributeWidth); + this.setDistribution(NSStackViewDistributionFill); + this.setSpacing(0.); + } + this + }; + + unsafe { + content_view.addArrangedSubview(&nav_bar); + content_view.addArrangedSubview(&web_view); + } + + // create the app delegate + let delegate = { + let this = Delegate::alloc(); + Delegate::initWithTextField_andWebView(this, &nav_url, &web_view) + }; + + unsafe { + // handle input from text field (on , load URL from text field in web view) + let object = ProtocolObject::from_ref(&*delegate); + nav_url.setDelegate(Some(object)); + + // handle nav events from web view (on finished navigating, update text area with current URL) + let object = ProtocolObject::from_ref(&*delegate); + web_view.setNavigationDelegate(Some(object)); + } + + // create the menu with a "quit" entry + unsafe { + let menu = NSMenu::initWithTitle(NSMenu::alloc(), ns_string!("")); + let menu_app_item = NSMenuItem::initWithTitle_action_keyEquivalent( + NSMenuItem::alloc(), + ns_string!(""), + None, + ns_string!(""), + ); + let menu_app_menu = NSMenu::initWithTitle(NSMenu::alloc(), ns_string!("")); + menu_app_menu.addItemWithTitle_action_keyEquivalent( + ns_string!("Quit"), + Some(sel!(terminate:)), + ns_string!("q"), + ); + menu_app_item.setSubmenu(Some(&menu_app_menu)); + menu.addItem(&menu_app_item); + app.setMainMenu(Some(&menu)); + } + + // configure the window + unsafe { + window.setContentView(Some(&content_view)); + window.center(); + window.setTitle(ns_string!("browser example")); + window.makeKeyAndOrderFront(None); + } + + // request the web view navigate to a page + unsafe { + let request = { + let url_string = ns_string!("https://google.com"); + let url = NSURL::URLWithString(url_string).expect("URL should parse"); + NSURLRequest::requestWithURL(&url) + }; + web_view.loadRequest(&request); + } + + // run the app + unsafe { app.run() }; +} diff --git a/crates/icrate/src/WebKit/fixes.rs b/crates/icrate/src/WebKit/fixes.rs index 677d97a4b..85e351279 100644 --- a/crates/icrate/src/WebKit/fixes.rs +++ b/crates/icrate/src/WebKit/fixes.rs @@ -1,4 +1,5 @@ use crate::common::*; +#[cfg(feature = "WebKit_WKNavigationAction")] use crate::WebKit::*; extern_methods!(