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:
parent
1b0be74fc8
commit
420422187a
84
examples/browser.rs
Normal file
84
examples/browser.rs
Normal 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();
|
||||||
|
}
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue