Fixes #6 and updates WebView to new layout system.

- Includes a (basic) `browser` example to make testing the feature
  easier.
This commit is contained in:
Ryan McGrath 2021-04-08 17:54:32 -07:00
parent 1b0be74fc8
commit 420422187a
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
2 changed files with 158 additions and 40 deletions

84
examples/browser.rs Normal file
View file

@ -0,0 +1,84 @@
//! This example showcases setting up a basic application and window, setting up some views to
//! work with autolayout, and some basic ways to handle colors.
use cacao::webview::WebView;
use cacao::macos::{App, AppDelegate};
use cacao::macos::menu::{Menu, MenuItem};
use cacao::macos::window::{Window, WindowConfig, WindowDelegate};
struct BasicApp {
window: Window<AppWindow>
}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
App::set_menu(vec![
Menu::new("", vec![
MenuItem::Services,
MenuItem::Separator,
MenuItem::Hide,
MenuItem::HideOthers,
MenuItem::ShowAll,
MenuItem::Separator,
MenuItem::Quit
]),
Menu::new("File", vec![
MenuItem::CloseWindow
]),
Menu::new("Edit", vec![
MenuItem::Undo,
MenuItem::Redo,
MenuItem::Separator,
MenuItem::Cut,
MenuItem::Copy,
MenuItem::Paste,
MenuItem::Separator,
MenuItem::SelectAll
]),
// Sidebar option is 11.0+ only.
Menu::new("View", vec![
MenuItem::EnterFullScreen
]),
Menu::new("Window", vec![
MenuItem::Minimize,
MenuItem::Zoom,
MenuItem::Separator,
MenuItem::new("Bring All to Front")
]),
Menu::new("Help", vec![])
]);
App::activate();
self.window.show();
}
}
#[derive(Default)]
struct AppWindow {
content: WebView
}
impl WindowDelegate for AppWindow {
const NAME: &'static str = "WindowDelegate";
fn did_load(&mut self, window: Window) {
window.set_title("Browser Example");
window.set_minimum_content_size(400., 400.);
window.set_content_view(&self.content);
self.content.load_url("https://www.duckduckgo.com/");
}
}
fn main() {
App::new("com.test.window", BasicApp {
window: Window::with(WindowConfig::default(), AppWindow::default())
}).run();
}

View file

@ -22,6 +22,8 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSString}; use crate::foundation::{id, nil, YES, NO, NSString};
use crate::geometry::Rect; use crate::geometry::Rect;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::layer::Layer;
use crate::utils::properties::ObjcProperty;
mod actions; mod actions;
pub use actions::*; pub use actions::*;
@ -54,13 +56,15 @@ fn allocate_webview(
// Technically private! // Technically private!
#[cfg(feature = "webview-downloading-macos")] #[cfg(feature = "webview-downloading-macos")]
let process_pool: id = msg_send![configuration, processPool]; let process_pool: id = msg_send![configuration, processPool];
// Technically private!
#[cfg(feature = "webview-downloading-macos")] #[cfg(feature = "webview-downloading-macos")]
let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate]; let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate];
let content_controller: id = msg_send![configuration, userContentController]; let content_controller: id = msg_send![configuration, userContentController];
for handler in handlers { for handler in handlers {
let name = NSString::new(&handler); let name = NSString::new(&handler);
let _: () = msg_send![content_controller, addScriptMessageHandler:*delegate name:name]; let _: () = msg_send![content_controller, addScriptMessageHandler:*delegate name:&*name];
} }
} }
@ -80,8 +84,16 @@ fn allocate_webview(
} }
pub struct WebView<T = ()> { pub struct WebView<T = ()> {
/// An internal flag for whether an instance of a View<T> is a handle. Typically, there's only
/// one instance that should have this set to `false` - if that one drops, we need to know to
/// do some extra cleanup.
pub is_handle: bool,
/// A pointer to the Objective-C runtime view controller. /// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>, pub objc: ObjcProperty,
/// A reference to the underlying CALayer.
pub layer: Layer,
/// We need to store the underlying delegate separately from the `WKWebView` - this is a where /// We need to store the underlying delegate separately from the `WKWebView` - this is a where
/// we do so. /// we do so.
@ -96,9 +108,15 @@ pub struct WebView<T = ()> {
/// A pointer to the Objective-C runtime leading layout constraint. /// A pointer to the Objective-C runtime leading layout constraint.
pub leading: LayoutAnchorX, pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime left layout constraint.
pub left: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint. /// A pointer to the Objective-C runtime trailing layout constraint.
pub trailing: LayoutAnchorX, pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime right layout constraint.
pub right: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint. /// A pointer to the Objective-C runtime bottom layout constraint.
pub bottom: LayoutAnchorY, pub bottom: LayoutAnchorY,
@ -122,22 +140,47 @@ impl Default for WebView {
} }
impl WebView { impl WebView {
pub fn new(config: WebViewConfig) -> Self { /// An internal initializer method for very common things that we need to do, regardless of
let view = allocate_webview(config, None); /// what type the end user is creating.
///
/// This handles grabbing autolayout anchor pointers, as well as things related to layering and
/// so on. It returns a generic `WebView<T>`, which the caller can then customize as needed.
pub(crate) fn init<T>(view: id) -> WebView<T> {
unsafe {
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
#[cfg(target_os = "macos")]
let _: () = msg_send![view, setWantsLayer:YES];
}
WebView { WebView {
is_handle: false,
delegate: None, delegate: None,
objc_delegate: None, objc_delegate: None,
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }), top: LayoutAnchorY::top(view),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }), left: LayoutAnchorX::left(view),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }), leading: LayoutAnchorX::leading(view),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }), right: LayoutAnchorX::right(view),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }), trailing: LayoutAnchorX::trailing(view),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }), bottom: LayoutAnchorY::bottom(view),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }), width: LayoutAnchorDimension::width(view),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }), height: LayoutAnchorDimension::height(view),
objc: unsafe { ShareId::from_ptr(view) }, center_x: LayoutAnchorX::center(view),
} center_y: LayoutAnchorY::center(view),
layer: Layer::wrap(unsafe {
msg_send![view, layer]
}),
objc: ObjcProperty::retain(view),
}
}
/// Returns a default `WebView`, suitable for customizing and displaying.
pub fn new(config: WebViewConfig) -> Self {
let view = allocate_webview(config, None);
WebView::init(view)
} }
} }
@ -155,20 +198,7 @@ impl<T> WebView<T> where T: WebViewDelegate + 'static {
}; };
let view = allocate_webview(config, Some(&objc_delegate)); let view = allocate_webview(config, Some(&objc_delegate));
let mut view = WebView::init(view);
let mut view = WebView {
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) },
};
&delegate.did_load(view.clone_as_handle()); &delegate.did_load(view.clone_as_handle());
view.delegate = Some(delegate); view.delegate = Some(delegate);
@ -186,12 +216,16 @@ impl<T> WebView<T> {
delegate: None, delegate: None,
top: self.top.clone(), top: self.top.clone(),
leading: self.leading.clone(), leading: self.leading.clone(),
left: self.left.clone(),
right: self.right.clone(),
is_handle: true,
trailing: self.trailing.clone(), trailing: self.trailing.clone(),
bottom: self.bottom.clone(), bottom: self.bottom.clone(),
width: self.width.clone(), width: self.width.clone(),
height: self.height.clone(), height: self.height.clone(),
center_x: self.center_x.clone(), center_x: self.center_x.clone(),
center_y: self.center_y.clone(), center_y: self.center_y.clone(),
layer: self.layer.clone(),
objc: self.objc.clone(), objc: self.objc.clone(),
objc_delegate: None objc_delegate: None
} }
@ -202,23 +236,21 @@ impl<T> WebView<T> {
pub fn load_url(&self, url: &str) { pub fn load_url(&self, url: &str) {
let url = NSString::new(url); let url = NSString::new(url);
unsafe { self.objc.with_mut(|obj| unsafe {
let u: id = msg_send![class!(NSURL), URLWithString:&*url]; let u: id = msg_send![class!(NSURL), URLWithString:&*url];
let request: id = msg_send![class!(NSURLRequest), requestWithURL:u]; let request: id = msg_send![class!(NSURLRequest), requestWithURL:u];
let _: () = msg_send![&*self.objc, loadRequest:request]; let _: () = msg_send![&*obj, loadRequest:request];
} });
} }
} }
impl<T> Layout for WebView<T> { impl<T> Layout for WebView<T> {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
// @TODO: Fix. self.objc.with_mut(handler);
//self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R { fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
// @TODO: Fix. self.objc.get(handler)
//self.objc.get(handler)
} }
/// Currently, this is a noop. Theoretically there is reason to support this, but in practice /// Currently, this is a noop. Theoretically there is reason to support this, but in practice
@ -235,11 +267,13 @@ impl<T> std::fmt::Debug for WebView<T> {
impl<T> Drop for WebView<T> { impl<T> Drop for WebView<T> {
/// A bit of extra cleanup for delegate callback pointers. /// A bit of extra cleanup for delegate callback pointers.
fn drop(&mut self) { fn drop(&mut self) {
if self.delegate.is_some() { if !self.is_handle {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setNavigationDelegate:nil]; let _: () = msg_send![&*obj, setNavigationDelegate:nil];
let _: () = msg_send![&*self.objc, setUIDelegate:nil]; let _: () = msg_send![&*obj, setUIDelegate:nil];
} });
self.remove_from_superview();
} }
} }
} }