diff --git a/.changes/reparent.md b/.changes/reparent.md new file mode 100644 index 000000000..040b1d108 --- /dev/null +++ b/.changes/reparent.md @@ -0,0 +1,5 @@ +--- +"wry": minor +--- + +Added `WebViewExtMacOS::reparent`,`WebViewExtWindows::reparent` and `WebViewExtUnix::reparent`. diff --git a/examples/reparent.rs b/examples/reparent.rs new file mode 100644 index 000000000..edb8a6717 --- /dev/null +++ b/examples/reparent.rs @@ -0,0 +1,107 @@ +// Copyright 2020-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +use tao::{ + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::Key, + window::WindowBuilder, +}; +use wry::WebViewBuilder; + +#[cfg(target_os = "macos")] +use {tao::platform::macos::WindowExtMacOS, wry::WebViewExtMacOS}; +#[cfg(target_os = "windows")] +use {tao::platform::windows::WindowExtWindows, wry::WebViewExtWindows}; + +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" +)))] +#[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" +)))] +use { + tao::platform::unix::WindowExtUnix, + wry::{WebViewBuilderExtUnix, WebViewExtUnix}, +}; + +fn main() -> wry::Result<()> { + let event_loop = EventLoop::new(); + let window = WindowBuilder::new().build(&event_loop).unwrap(); + let window2 = WindowBuilder::new().build(&event_loop).unwrap(); + + #[cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + ))] + let builder = WebViewBuilder::new(&window); + + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + let builder = { + let vbox = window.default_vbox().unwrap(); + WebViewBuilder::new_gtk(vbox) + }; + + let webview = builder.with_url("https://tauri.app")?.build()?; + + let mut webview_container = window.id(); + + event_loop.run(move |event, _event_loop, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character("x"), + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } => { + let new_parent = if webview_container == window.id() { + &window2 + } else { + &window + }; + webview_container = new_parent.id(); + + #[cfg(target_os = "macos")] + webview.reparent(new_parent.ns_window() as cocoa::base::id); + #[cfg(not(any( + target_os = "windows", + target_os = "macos", + target_os = "ios", + target_os = "android" + )))] + webview.reparent(new_parent.default_vbox().unwrap()); + #[cfg(target_os = "windows")] + webview.reparent(new_parent.hwnd()); + } + _ => {} + } + }); +} diff --git a/src/lib.rs b/src/lib.rs index 1ec0e3231..661d2061f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1458,6 +1458,9 @@ pub trait WebViewExtWindows { /// [1]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2memoryusagetargetlevel /// [2]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.memoryusagetargetlevel?view=webview2-dotnet-1.0.2088.41#remarks fn set_memory_usage_level(&self, level: MemoryUsageLevel); + + /// Attaches this webview to the given HWND and removes it from the current one. + fn reparent(&self, hwnd: isize); } #[cfg(target_os = "windows")] @@ -1473,6 +1476,10 @@ impl WebViewExtWindows for WebView { fn set_memory_usage_level(&self, level: MemoryUsageLevel) { self.webview.set_memory_usage_level(level); } + + fn reparent(&self, hwnd: isize) { + self.webview.reparent(hwnd) + } } /// Additional methods on `WebView` that are specific to Linux. @@ -1494,6 +1501,11 @@ pub trait WebViewExtUnix: Sized { /// Returns Webkit2gtk Webview handle fn webview(&self) -> webkit2gtk::WebView; + + /// Attaches this webview to the given Widget and removes it from the current one. + fn reparent(&self, widget: &W) + where + W: gtk::prelude::IsA; } #[cfg(gtk)] @@ -1508,6 +1520,13 @@ impl WebViewExtUnix for WebView { fn webview(&self) -> webkit2gtk::WebView { self.webview.webview.clone() } + + fn reparent(&self, widget: &W) + where + W: gtk::prelude::IsA, + { + self.webview.reparent(widget) + } } /// Additional methods on `WebView` that are specific to macOS. @@ -1519,6 +1538,8 @@ pub trait WebViewExtMacOS { fn manager(&self) -> cocoa::base::id; /// Returns NSWindow associated with the WKWebView webview fn ns_window(&self) -> cocoa::base::id; + /// Attaches this webview to the given NSWindow and removes it from the current one. + fn reparent(&self, window: cocoa::base::id); } #[cfg(target_os = "macos")] @@ -1532,7 +1553,14 @@ impl WebViewExtMacOS for WebView { } fn ns_window(&self) -> cocoa::base::id { - self.webview.ns_window + unsafe { + let ns_window: cocoa::base::id = msg_send![self.webview.webview, window]; + ns_window + } + } + + fn reparent(&self, window: cocoa::base::id) { + self.webview.reparent_to(window) } } diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index ba9b4a1f8..f4261df38 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -721,6 +721,32 @@ impl InnerWebView { pub fn focus(&self) { self.webview.grab_focus(); } + + pub fn reparent(&self, container: &W) + where + W: gtk::prelude::IsA, + { + if let Some(parent) = self + .webview + .parent() + .and_then(|p| p.dynamic_cast::().ok()) + { + parent.remove(&self.webview); + if container.type_().name() == "GtkBox" { + container + .dynamic_cast_ref::() + .unwrap() + .pack_start(&self.webview, true, true, 0); + } else if container.type_().name() == "GtkFixed" { + container + .dynamic_cast_ref::() + .unwrap() + .put(&self.webview, 0, 0); + } else { + container.add(&self.webview); + } + } + } } pub fn platform_webview_version() -> Result { diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 1e68b82a6..e21f95edf 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -1107,6 +1107,10 @@ impl InnerWebView { let level = COREWEBVIEW2_MEMORY_USAGE_TARGET_LEVEL(level); let _ = unsafe { webview.SetMemoryUsageTargetLevel(level) }; } + + pub fn reparent(&self, hwnd: isize) { + let _ = unsafe { self.controller.SetParentWindow(HWND(hwnd as _)) }; + } } unsafe fn prepare_web_request_response( diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 0607e2b72..7139d4445 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -73,8 +73,6 @@ const NS_JSON_WRITING_FRAGMENTS_ALLOWED: u64 = 4; pub(crate) struct InnerWebView { pub webview: id, - #[cfg(target_os = "macos")] - pub ns_window: id, pub manager: id, is_child: bool, pending_scripts: Arc>>>, @@ -826,7 +824,7 @@ impl InnerWebView { // ns window is required for the print operation #[cfg(target_os = "macos")] - let ns_window = { + { let ns_window: id = msg_send![ns_view, window]; let can_set_titlebar_style: BOOL = msg_send![ @@ -837,14 +835,10 @@ impl InnerWebView { // `1` means `none`, see https://developer.apple.com/documentation/appkit/nstitlebarseparatorstyle/none let () = msg_send![ns_window, setTitlebarSeparatorStyle: 1]; } - - ns_window - }; + } let w = Self { webview, - #[cfg(target_os = "macos")] - ns_window, manager, pending_scripts, ipc_handler_ptr, @@ -1075,7 +1069,8 @@ r#"Object.defineProperty(window, 'ipc', { // Allow the modal to detach from the current thread and be non-blocker let () = msg_send![print_operation, setCanSpawnSeparateThread: YES]; // Launch the modal - let () = msg_send![print_operation, runOperationModalForWindow: self.ns_window delegate: null::<*const c_void>() didRunSelector: null::<*const c_void>() contextInfo: null::<*const c_void>()]; + let window: id = msg_send![self.webview, window]; + let () = msg_send![print_operation, runOperationModalForWindow: window delegate: null::<*const c_void>() didRunSelector: null::<*const c_void>() contextInfo: null::<*const c_void>()]; } } } @@ -1167,6 +1162,13 @@ r#"Object.defineProperty(window, 'ipc', { let _: () = msg_send![window, makeFirstResponder: self.webview]; } } + + pub(crate) fn reparent_to(&self, window: id) { + unsafe { + let content_view: id = msg_send![window, contentView]; + let _: () = msg_send![content_view, addSubview: self.webview]; + } + } } pub fn url_from_webview(webview: id) -> String {