Преглед на файлове

Support window parenting on macOS, closes #3751 (#3754)

Co-authored-by: Amr Bashir <amr.bashir2015@gmail.com>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
Kasper преди 3 години
родител
ревизия
4e807a53e2

+ 5 - 0
.changes/parent-window-hwnd.md

@@ -0,0 +1,5 @@
+---
+"tauri": patch
+---
+
+**Breaking change:** The `Window::hwnd` method now returns *HWND* from `windows-rs` crate instead of *c_void* on Windows.

+ 7 - 0
.changes/parent-window-macos.md

@@ -0,0 +1,7 @@
+---
+"tauri-runtime-wry": minor
+"tauri-runtime": minor
+"tauri": minor
+---
+
+Support window parenting on macOS

+ 8 - 0
core/tauri-runtime-wry/src/lib.rs

@@ -877,6 +877,14 @@ impl WindowBuilder for WindowBuilderWrapper {
     self
   }
 
+  #[cfg(target_os = "macos")]
+  fn parent_window(mut self, parent: *mut std::ffi::c_void) -> Self {
+    use wry::application::platform::macos::WindowBuilderExtMacOS;
+
+    self.inner = self.inner.with_parent_window(parent);
+    self
+  }
+
   #[cfg(windows)]
   fn owner_window(mut self, owner: HWND) -> Self {
     self.inner = self.inner.with_owner_window(owner);

+ 9 - 0
core/tauri-runtime/src/webview.rs

@@ -165,6 +165,15 @@ pub trait WindowBuilder: WindowBuilderBase {
   #[must_use]
   fn parent_window(self, parent: HWND) -> Self;
 
+  /// Sets a parent to the window to be created.
+  ///
+  /// A child window has the WS_CHILD style and is confined to the client area of its parent window.
+  ///
+  /// For more information, see <https://docs.microsoft.com/en-us/windows/win32/winmsg/window-features#child-windows>
+  #[cfg(target_os = "macos")]
+  #[must_use]
+  fn parent_window(self, parent: *mut std::ffi::c_void) -> Self;
+
   /// Set an owner to the window to be created.
   ///
   /// From MSDN:

+ 4 - 0
core/tauri/Cargo.toml

@@ -277,6 +277,10 @@ name = "multiwindow"
 path = "../../examples/multiwindow/src-tauri/src/main.rs"
 required-features = [ "window-create" ]
 
+[[example]]
+name = "parent-window"
+path = "../../examples/parent-window/src-tauri/src/main.rs"
+
 [[example]]
 name = "navigation"
 path = "../../examples/navigation/src-tauri/src/main.rs"

+ 5 - 0
core/tauri/src/test/mock_runtime.rs

@@ -238,6 +238,11 @@ impl WindowBuilder for MockWindowBuilder {
     self
   }
 
+  #[cfg(target_os = "macos")]
+  fn parent_window(self, parent: *mut std::ffi::c_void) -> Self {
+    self
+  }
+
   #[cfg(windows)]
   fn owner_window(self, owner: HWND) -> Self {
     self

+ 11 - 8
core/tauri/src/window.rs

@@ -387,6 +387,14 @@ impl<R: Runtime> WindowBuilder<R> {
     self
   }
 
+  /// Sets a parent to the window to be created.
+  #[cfg(target_os = "macos")]
+  #[must_use]
+  pub fn parent_window(mut self, parent: *mut std::ffi::c_void) -> Self {
+    self.window_builder = self.window_builder.parent_window(parent);
+    self
+  }
+
   /// Set an owner to the window to be created.
   ///
   /// From MSDN:
@@ -463,7 +471,7 @@ unsafe impl<R: Runtime> raw_window_handle::HasRawWindowHandle for Window<R> {
   #[cfg(windows)]
   fn raw_window_handle(&self) -> raw_window_handle::RawWindowHandle {
     let mut handle = raw_window_handle::Win32Handle::empty();
-    handle.hwnd = self.hwnd().expect("failed to get window `hwnd`");
+    handle.hwnd = self.hwnd().expect("failed to get window `hwnd`").0 as *mut _;
     raw_window_handle::RawWindowHandle::Win32(handle)
   }
 
@@ -909,13 +917,8 @@ impl<R: Runtime> Window<R> {
   }
   /// Returns the native handle that is used by this window.
   #[cfg(windows)]
-  pub fn hwnd(&self) -> crate::Result<*mut std::ffi::c_void> {
-    self
-      .window
-      .dispatcher
-      .hwnd()
-      .map(|hwnd| hwnd.0 as *mut _)
-      .map_err(Into::into)
+  pub fn hwnd(&self) -> crate::Result<HWND> {
+    self.window.dispatcher.hwnd().map_err(Into::into)
   }
 
   /// Returns the `ApplicatonWindow` from gtk crate that is used by this window.

+ 3 - 0
examples/parent-window/README.md

@@ -0,0 +1,3 @@
+# Parent Window Example
+
+Run the following at the root directory of the repository to try it out: `cargo run --example parent-window`.

+ 53 - 0
examples/parent-window/index.html

@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+  <style>
+    #response {
+      white-space: pre-wrap;
+    }
+  </style>
+</head>
+
+<body>
+  <div id="window-label"></div>
+  <div id="container"></div>
+  <div id="response"></div>
+
+  <script>
+    var WebviewWindow = window.__TAURI__.window.WebviewWindow
+    var thisTauriWindow = window.__TAURI__.window.getCurrent()
+    var windowLabel = thisTauriWindow.label
+    var windowLabelContainer = document.getElementById('window-label')
+    windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
+
+    var container = document.getElementById('container')
+
+    var responseContainer = document.getElementById('response')
+    function runCommand(commandName, args, optional) {
+      window.__TAURI__
+        .invoke(commandName, args)
+        .then((response) => {
+          responseContainer.innerText += `Ok(${response})\n\n`
+        })
+        .catch((error) => {
+          responseContainer.innerText += `Err(${error})\n\n`
+        })
+    }
+    window.__TAURI__.event.listen('tauri://window-created', function (event) {
+      responseContainer.innerText += 'Got window-created event\n\n'
+    })
+
+    var createWindowButton = document.createElement('button')
+    var windowNumber = 1
+    createWindowButton.innerHTML = 'Create child window '+windowNumber
+    createWindowButton.addEventListener('click', function () {
+      runCommand('create_child_window', { id: 'child-'+windowNumber })
+      windowNumber += 1
+      createWindowButton.innerHTML = 'Create child window '+windowNumber
+    })
+    container.appendChild(createWindowButton)
+  </script>
+</body>
+
+</html>

+ 4 - 0
examples/parent-window/src-tauri/.gitignore

@@ -0,0 +1,4 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+WixTools

+ 3 - 0
examples/parent-window/src-tauri/.license_template

@@ -0,0 +1,3 @@
+// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT

+ 17 - 0
examples/parent-window/src-tauri/Cargo.toml

@@ -0,0 +1,17 @@
+[package]
+name = "parent-window"
+version = "0.1.0"
+description = "An example Tauri Multi-Window Application"
+edition = "2021"
+rust-version = "1.57"
+license = "Apache-2.0 OR MIT"
+
+[build-dependencies]
+tauri-build = { path = "../../../core/tauri-build" }
+
+[dependencies]
+tauri = { path = "../../../core/tauri" }
+
+[features]
+default = [ "custom-protocol" ]
+custom-protocol = [ "tauri/custom-protocol" ]

+ 14 - 0
examples/parent-window/src-tauri/build.rs

@@ -0,0 +1,14 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use tauri_build::{try_build, Attributes, WindowsAttributes};
+
+fn main() {
+  if let Err(error) = try_build(
+    Attributes::new()
+      .windows_attributes(WindowsAttributes::new().window_icon_path("../../.icons/icon.ico")),
+  ) {
+    panic!("error found during tauri-build: {:#?}", error);
+  }
+}

+ 22 - 0
examples/parent-window/src-tauri/src/commands.rs

@@ -0,0 +1,22 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+use tauri::{command, window, AppHandle, Manager, WindowUrl};
+
+#[command]
+pub fn create_child_window(id: String, app: AppHandle) {
+  #[cfg(any(windows, target_os = "macos"))]
+  let main = app.get_window("main").unwrap();
+
+  let child = window::WindowBuilder::new(&app, id, WindowUrl::default())
+    .title("Child")
+    .inner_size(400.0, 300.0);
+
+  #[cfg(target_os = "macos")]
+  let child = child.parent_window(main.ns_window().unwrap());
+  #[cfg(windows)]
+  let child = child.parent_window(main.hwnd().unwrap());
+
+  child.build().unwrap();
+}

+ 38 - 0
examples/parent-window/src-tauri/src/main.rs

@@ -0,0 +1,38 @@
+// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+#![cfg_attr(
+  all(not(debug_assertions), target_os = "windows"),
+  windows_subsystem = "windows"
+)]
+
+use tauri::{WindowBuilder, WindowUrl};
+
+mod commands;
+
+fn main() {
+  tauri::Builder::default()
+    .on_page_load(|window, _payload| {
+      let label = window.label().to_string();
+      window.listen("clicked".to_string(), move |_payload| {
+        println!("got 'clicked' event on window '{}'", label);
+      });
+    })
+    .invoke_handler(tauri::generate_handler![commands::create_child_window])
+    .create_window(
+      "main".to_string(),
+      WindowUrl::default(),
+      |window_builder, webview_attributes| {
+        (
+          window_builder.title("Main").inner_size(600.0, 400.0),
+          webview_attributes,
+        )
+      },
+    )
+    .unwrap() // safe to unwrap: window label is valid
+    .run(tauri::generate_context!(
+      "../../examples/parent-window/src-tauri/tauri.conf.json"
+    ))
+    .expect("failed to run tauri application");
+}

+ 36 - 0
examples/parent-window/src-tauri/tauri.conf.json

@@ -0,0 +1,36 @@
+{
+  "build": {
+    "distDir": [
+      "../index.html"
+    ],
+    "devPath": [
+      "../index.html"
+    ],
+    "withGlobalTauri": true
+  },
+  "tauri": {
+    "bundle": {
+      "active": true,
+      "targets": "all",
+      "identifier": "com.tauri.dev",
+      "icon": [
+        "../../.icons/32x32.png",
+        "../../.icons/128x128.png",
+        "../../.icons/128x128@2x.png",
+        "../../.icons/icon.icns",
+        "../../.icons/icon.ico"
+      ],
+      "resources": [],
+      "externalBin": [],
+      "copyright": "",
+      "category": "DeveloperTool"
+    },
+    "allowlist": {},
+    "security": {
+      "csp": "default-src 'self'"
+    },
+    "updater": {
+      "active": false
+    }
+  }
+}