Skip to content

Commit

Permalink
feat(macos): add tabbing_identifier option, closes #2804, #3912 (#5399
Browse files Browse the repository at this point in the history
)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
  • Loading branch information
caesar and lucasfernog committed Oct 19, 2022
1 parent bddf59e commit 4137ab4
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 75 deletions.
6 changes: 6 additions & 0 deletions .changes/automatic-tabbing.md
@@ -0,0 +1,6 @@
---
"tauri": minor
"tauri-runtime-wry": minor
---

Disable automatic window tabbing on macOS when the `tabbing_identifier` option is not defined, the window is transparent or does not have decorations.
5 changes: 5 additions & 0 deletions .changes/tabbing-identifier-api.md
@@ -0,0 +1,5 @@
---
"api": minor
---

Added `tabbingIdentifier` window option for macOS.
8 changes: 8 additions & 0 deletions .changes/tabbing-identifier.md
@@ -0,0 +1,8 @@
---
"tauri": minor
"tauri-runtime": minor
"tauri-runtime-wry": minor
"api": minor
---

Added `tabbing_identifier` to the window builder on macOS.
22 changes: 22 additions & 0 deletions core/tauri-runtime-wry/src/lib.rs
Expand Up @@ -680,6 +680,8 @@ impl From<CursorIcon> for CursorIconWrapper {
pub struct WindowBuilderWrapper {
inner: WryWindowBuilder,
center: bool,
#[cfg(target_os = "macos")]
tabbing_identifier: Option<String>,
menu: Option<Menu>,
}

Expand Down Expand Up @@ -711,6 +713,9 @@ impl WindowBuilder for WindowBuilderWrapper {
window = window
.hidden_title(config.hidden_title)
.title_bar_style(config.title_bar_style);
if let Some(identifier) = &config.tabbing_identifier {
window = window.tabbing_identifier(identifier);
}
}

#[cfg(any(not(target_os = "macos"), feature = "macos-private-api"))]
Expand Down Expand Up @@ -878,6 +883,13 @@ impl WindowBuilder for WindowBuilderWrapper {
self
}

#[cfg(target_os = "macos")]
fn tabbing_identifier(mut self, identifier: &str) -> Self {
self.inner = self.inner.with_tabbing_identifier(identifier);
self.tabbing_identifier.replace(identifier.into());
self
}

fn icon(mut self, icon: Icon) -> Result<Self> {
self.inner = self
.inner
Expand Down Expand Up @@ -2930,6 +2942,16 @@ fn create_webview<T: UserEvent>(
.with_drag_and_drop(webview_attributes.file_drop_handler_enabled);
}

#[cfg(target_os = "macos")]
{
if window_builder.tabbing_identifier.is_none()
|| window_builder.inner.window.transparent
|| !window_builder.inner.window.decorations
{
window_builder.inner = window_builder.inner.with_automatic_window_tabbing(false);
}
}

let is_window_transparent = window_builder.inner.window.transparent;
let menu_items = if let Some(menu) = window_builder.menu {
let mut menu_items = HashMap::new();
Expand Down
10 changes: 10 additions & 0 deletions core/tauri-runtime/src/webview.rs
Expand Up @@ -219,6 +219,16 @@ pub trait WindowBuilder: WindowBuilderBase {
#[must_use]
fn hidden_title(self, hidden: bool) -> Self;

/// Defines the window [tabbing identifier] for macOS.
///
/// Windows with matching tabbing identifiers will be grouped together.
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
///
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
#[cfg(target_os = "macos")]
#[must_use]
fn tabbing_identifier(self, identifier: &str) -> Self;

/// Forces a theme or uses the system settings if None was provided.
fn theme(self, theme: Option<Theme>) -> Self;

Expand Down
13 changes: 12 additions & 1 deletion core/tauri-utils/src/config.rs
Expand Up @@ -873,6 +873,14 @@ pub struct WindowConfig {
/// Whether clicking an inactive window also clicks through to the webview.
#[serde(default, alias = "accept-first-mouse")]
pub accept_first_mouse: bool,
/// Defines the window [tabbing identifier] for macOS.
///
/// Windows with matching tabbing identifiers will be grouped together.
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
///
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
#[serde(default, alias = "tabbing-identifier")]
pub tabbing_identifier: Option<String>,
}

impl Default for WindowConfig {
Expand Down Expand Up @@ -905,6 +913,7 @@ impl Default for WindowConfig {
title_bar_style: Default::default(),
hidden_title: false,
accept_first_mouse: false,
tabbing_identifier: None,
}
}
}
Expand Down Expand Up @@ -3037,6 +3046,7 @@ mod build {
let title_bar_style = &self.title_bar_style;
let hidden_title = self.hidden_title;
let accept_first_mouse = self.accept_first_mouse;
let tabbing_identifier = opt_str_lit(self.tabbing_identifier.as_ref());

literal_struct!(
tokens,
Expand Down Expand Up @@ -3067,7 +3077,8 @@ mod build {
theme,
title_bar_style,
hidden_title,
accept_first_mouse
accept_first_mouse,
tabbing_identifier
);
}
}
Expand Down
5 changes: 5 additions & 0 deletions core/tauri/src/test/mock_runtime.rs
Expand Up @@ -283,6 +283,11 @@ impl WindowBuilder for MockWindowBuilder {
self
}

#[cfg(target_os = "macos")]
fn tabbing_identifier(self, identifier: &str) -> Self {
self
}

fn theme(self, theme: Option<Theme>) -> Self {
self
}
Expand Down
13 changes: 13 additions & 0 deletions core/tauri/src/window.rs
Expand Up @@ -462,6 +462,19 @@ impl<'a, R: Runtime> WindowBuilder<'a, R> {
self
}

/// Defines the window [tabbing identifier] for macOS.
///
/// Windows with matching tabbing identifiers will be grouped together.
/// If the tabbing identifier is not set, automatic tabbing will be disabled.
///
/// [tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
#[cfg(target_os = "macos")]
#[must_use]
pub fn tabbing_identifier(mut self, identifier: &str) -> Self {
self.window_builder = self.window_builder.tabbing_identifier(identifier);
self
}

// ------------------------------------------- Webview attributes -------------------------------------------

/// Adds the provided JavaScript to a list of scripts that should be run after the global object has been created,
Expand Down
145 changes: 75 additions & 70 deletions examples/multiwindow/index.html
@@ -1,84 +1,89 @@
<!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>
<head>
<style>
#response {
white-space: pre-wrap;
}
</style>
</head>

<script>
var WebviewWindow = window.__TAURI__.window.WebviewWindow
var appWindow = window.__TAURI__.window.appWindow
var windowLabel = appWindow.label
var windowLabelContainer = document.getElementById('window-label')
windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'
<body>
<div id="window-label"></div>
<div id="container"></div>
<div id="response"></div>

var container = document.getElementById('container')
<script>
var WebviewWindow = window.__TAURI__.window.WebviewWindow
var appWindow = window.__TAURI__.window.appWindow
var windowLabel = appWindow.label
var windowLabelContainer = document.getElementById('window-label')
windowLabelContainer.innerText = 'This is the ' + windowLabel + ' window.'

function createWindowMessageBtn(label) {
var tauriWindow = WebviewWindow.getByLabel(label)
var button = document.createElement('button')
button.innerText = 'Send message to ' + label
button.addEventListener('click', function () {
tauriWindow.emit('clicked', 'message from ' + windowLabel)
})
container.appendChild(button)
}
var container = document.getElementById('container')

// global listener
window.__TAURI__.event.listen('clicked', function (event) {
responseContainer.innerHTML +=
'Got ' + JSON.stringify(event) + ' on global listener\n\n'
})
window.__TAURI__.event.listen('tauri://window-created', function (event) {
createWindowMessageBtn(event.payload.label)
function createWindowMessageBtn(label) {
var tauriWindow = WebviewWindow.getByLabel(label)
var button = document.createElement('button')
button.innerText = 'Send message to ' + label
button.addEventListener('click', function () {
tauriWindow.emit('clicked', 'message from ' + windowLabel)
})
container.appendChild(button)
}

var responseContainer = document.getElementById('response')
// listener tied to this window
appWindow.listen('clicked', function (event) {
responseContainer.innerText +=
'Got ' + JSON.stringify(event) + ' on window listener\n\n'
})
// global listener
window.__TAURI__.event.listen('clicked', function (event) {
responseContainer.innerHTML +=
'Got ' + JSON.stringify(event) + ' on global listener\n\n'
})
window.__TAURI__.event.listen('tauri://window-created', function (event) {
createWindowMessageBtn(event.payload.label)
})

var createWindowButton = document.createElement('button')
createWindowButton.innerHTML = 'Create window'
createWindowButton.addEventListener('click', function () {
var webviewWindow = new WebviewWindow(
Math.random().toString().replace('.', '')
)
webviewWindow.once('tauri://created', function () {
responseContainer.innerHTML += 'Created new webview'
})
webviewWindow.once('tauri://error', function (e) {
responseContainer.innerHTML += 'Error creating new webview'
})
})
container.appendChild(createWindowButton)
var responseContainer = document.getElementById('response')
// listener tied to this window
appWindow.listen('clicked', function (event) {
responseContainer.innerText +=
'Got ' + JSON.stringify(event) + ' on window listener\n\n'
})

var globalMessageButton = document.createElement('button')
globalMessageButton.innerHTML = 'Send global message'
globalMessageButton.addEventListener('click', function () {
// emit to all windows
window.__TAURI__.event.emit('clicked', 'message from ' + windowLabel)
var createWindowButton = document.createElement('button')
createWindowButton.innerHTML = 'Create window'
createWindowButton.addEventListener('click', function () {
var webviewWindow = new WebviewWindow(
Math.random().toString().replace('.', ''),
{
tabbingIdentifier: windowLabel
}
)
webviewWindow.once('tauri://created', function () {
responseContainer.innerHTML += 'Created new webview'
})
webviewWindow.once('tauri://error', function (e) {
responseContainer.innerHTML += 'Error creating new webview'
})
container.appendChild(globalMessageButton)
})
container.appendChild(createWindowButton)

var allWindows = window.__TAURI__.window.getAll()
for (var index in allWindows) {
var label = allWindows[index].label
if (label === windowLabel) {
continue
}
createWindowMessageBtn(label)
var globalMessageButton = document.createElement('button')
globalMessageButton.innerHTML = 'Send global message'
globalMessageButton.addEventListener('click', function () {
// emit to all windows
window.__TAURI__.event.emit('clicked', 'message from ' + windowLabel)
})
container.appendChild(globalMessageButton)

var allWindows = window.__TAURI__.window.getAll()
for (var index in allWindows) {
var label = allWindows[index].label
if (label === windowLabel) {
continue
}
</script>
</body>
</html>
createWindowMessageBtn(label)
}
</script>
</body>

</html>
12 changes: 8 additions & 4 deletions examples/multiwindow/main.rs
Expand Up @@ -18,13 +18,17 @@ fn main() {
});
})
.setup(|app| {
WindowBuilder::new(
#[allow(unused_mut)]
let mut builder = WindowBuilder::new(
app,
"Rust".to_string(),
tauri::WindowUrl::App("index.html".into()),
)
.title("Tauri - Rust")
.build()?;
);
#[cfg(target_os = "macos")]
{
builder = builder.tabbing_identifier("Rust");
}
let _window = builder.title("Tauri - Rust").build()?;
Ok(())
})
.run(tauri::generate_context!(
Expand Down
2 changes: 2 additions & 0 deletions examples/multiwindow/tauri.conf.json
Expand Up @@ -35,12 +35,14 @@
{
"label": "Main",
"title": "Tauri - Main",
"tabbingIdentifier": "Main",
"width": 800,
"height": 600
},
{
"label": "Secondary",
"title": "Tauri - Secondary",
"tabbingIdentifier": "Secondary",
"width": 600,
"height": 400
}
Expand Down
7 changes: 7 additions & 0 deletions tooling/api/src/window.ts
Expand Up @@ -2046,6 +2046,13 @@ interface WindowOptions {
* Whether clicking an inactive window also clicks through to the webview.
*/
acceptFirstMouse?: boolean
/**
* Defines the window [tabbing identifier](https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier) on macOS.
*
* Windows with the same tabbing identifier will be grouped together.
* If the tabbing identifier is not set, automatic tabbing will be disabled.
*/
tabbingIdentifier?: string
/**
* The user agent for the webview.
*/
Expand Down
7 changes: 7 additions & 0 deletions tooling/cli/schema.json
Expand Up @@ -675,6 +675,13 @@
"description": "Whether clicking an inactive window also clicks through to the webview.",
"default": false,
"type": "boolean"
},
"tabbingIdentifier": {
"description": "Defines the window [tabbing identifier].\n\n[tabbing identifier]: <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>",
"type": [
"string",
"null"
]
}
},
"additionalProperties": false
Expand Down

0 comments on commit 4137ab4

Please sign in to comment.