Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support window parenting on macOS, closes #3751 #3754

Merged
merged 9 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/parent-window-hwnd.md
Original file line number Diff line number Diff line change
@@ -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 changes: 7 additions & 0 deletions .changes/parent-window-macos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"tauri-runtime-wry": minor
"tauri-runtime": minor
"tauri": minor
---

Support window parenting on macOS
8 changes: 8 additions & 0 deletions core/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions core/tauri-runtime/src/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
5 changes: 5 additions & 0 deletions core/tauri/src/test/mock_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 11 additions & 8 deletions core/tauri/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions examples/parent-window/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 53 additions & 0 deletions examples/parent-window/index.html
Original file line number Diff line number Diff line change
@@ -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 changes: 4 additions & 0 deletions examples/parent-window/src-tauri/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
3 changes: 3 additions & 0 deletions examples/parent-window/src-tauri/.license_template
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions examples/parent-window/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions examples/parent-window/src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 22 additions & 0 deletions examples/parent-window/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 38 additions & 0 deletions examples/parent-window/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 36 additions & 0 deletions examples/parent-window/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}