Skip to content

Commit ed32257

Browse files
feat(windows): add tabbed effect (#7794)
Co-authored-by: Lucas Nogueira <lucas@tauri.studio> Co-authored-by: Lucas Nogueira <lucas@tauri.app>
1 parent 880266a commit ed32257

File tree

17 files changed

+161
-501
lines changed

17 files changed

+161
-501
lines changed

.changes/api-tabbed.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@tauri-apps/api": "patch:feat"
3+
---
4+
5+
On Windows, add `Effect.Tabbed`,`Effect.TabbedDark` and `Effect.TabbedLight` effects.

.changes/tauri-tabbed.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": "patch:feat"
3+
---
4+
5+
On Windows, add `Effect::Tabbed`,`Effect::TabbedDark` and `Effect::TabbedLight` effects.

.changes/tauri-utils-tabbed.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-utils": "patch:feat"
3+
---
4+
5+
On Windows, add `WindowEffect::Tabbed`,`WindowEffect::TabbedDark` and `WindowEffect::TabbedLight`
6+

core/tauri-config-schema/schema.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,27 @@
796796
"micaLight"
797797
]
798798
},
799+
{
800+
"description": "Tabbed effect that matches the system dark perefence **Windows 11 Only**",
801+
"type": "string",
802+
"enum": [
803+
"tabbed"
804+
]
805+
},
806+
{
807+
"description": "Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**",
808+
"type": "string",
809+
"enum": [
810+
"tabbedDark"
811+
]
812+
},
813+
{
814+
"description": "Tabbed effect with light mode **Windows 11 Only**",
815+
"type": "string",
816+
"enum": [
817+
"tabbedLight"
818+
]
819+
},
799820
{
800821
"description": "**Windows 7/10/11(22H1) Only**\n\n## Notes\n\nThis effect has bad performance when resizing/dragging the window on Windows 11 build 22621.",
801822
"type": "string",

core/tauri-utils/src/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,12 @@ pub struct BundleConfig {
842842
#[serde(rename_all = "camelCase", deny_unknown_fields)]
843843
pub struct Color(pub u8, pub u8, pub u8, pub u8);
844844

845+
impl From<Color> for (u8, u8, u8, u8) {
846+
fn from(value: Color) -> Self {
847+
(value.0, value.1, value.2, value.3)
848+
}
849+
}
850+
845851
/// The window effects configuration object
846852
#[skip_serializing_none]
847853
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
@@ -2197,6 +2203,9 @@ mod build {
21972203
WindowEffect::MicaLight => quote! { #prefix::MicaLight},
21982204
WindowEffect::Blur => quote! { #prefix::Blur},
21992205
WindowEffect::Acrylic => quote! { #prefix::Acrylic},
2206+
WindowEffect::Tabbed => quote! { #prefix::Tabbed },
2207+
WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
2208+
WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
22002209
})
22012210
}
22022211
}

core/tauri-utils/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ mod window_effects {
127127
MicaDark,
128128
/// Mica effect with light mode **Windows 11 Only**
129129
MicaLight,
130+
/// Tabbed effect that matches the system dark perefence **Windows 11 Only**
131+
Tabbed,
132+
/// Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**
133+
TabbedDark,
134+
/// Tabbed effect with light mode **Windows 11 Only**
135+
TabbedLight,
130136
/// **Windows 7/10/11(22H1) Only**
131137
///
132138
/// ## Notes

core/tauri/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ infer = { version = "0.15", optional = true }
7171
png = { version = "0.17", optional = true }
7272
ico = { version = "0.3.0", optional = true }
7373
http-range = { version = "0.1.4", optional = true }
74+
window-vibrancy = "0.4"
7475

7576
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
7677
muda = { version = "0.8", default-features = false }

core/tauri/scripts/bundle.global.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/tauri/src/vibrancy/macos.rs

Lines changed: 13 additions & 232 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,17 @@
77

88
use crate::utils::config::WindowEffectsConfig;
99
use crate::window::{Effect, EffectState};
10-
use cocoa::{
11-
appkit::{
12-
NSAppKitVersionNumber, NSAppKitVersionNumber10_10, NSAppKitVersionNumber10_11,
13-
NSAutoresizingMaskOptions, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow,
14-
NSWindowOrderingMode,
15-
},
16-
base::{id, nil, BOOL},
17-
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize},
18-
};
19-
use objc::{class, msg_send, sel, sel_impl};
10+
use raw_window_handle::HasRawWindowHandle;
11+
use window_vibrancy::{NSVisualEffectMaterial, NSVisualEffectState};
2012

21-
pub fn apply_effects(window: id, effects: WindowEffectsConfig) {
13+
pub fn apply_effects(window: impl HasRawWindowHandle, effects: WindowEffectsConfig) {
2214
let WindowEffectsConfig {
2315
effects,
2416
radius,
2517
state,
2618
..
2719
} = effects;
28-
let mut appearance: NSVisualEffectMaterial = if let Some(effect) = effects.into_iter().find(|e| {
20+
let effect = if let Some(effect) = effects.into_iter().find(|e| {
2921
matches!(
3022
e,
3123
Effect::AppearanceBased
@@ -49,221 +41,14 @@ pub fn apply_effects(window: id, effects: WindowEffectsConfig) {
4941
| Effect::UnderPageBackground
5042
)
5143
}) {
52-
effect.into()
44+
effect
5345
} else {
5446
return;
5547
};
5648

57-
unsafe {
58-
if NSAppKitVersionNumber < NSAppKitVersionNumber10_10 {
59-
return;
60-
}
61-
62-
if !msg_send![class!(NSThread), isMainThread] {
63-
return;
64-
}
65-
66-
if appearance as u32 > 4 && NSAppKitVersionNumber < NSAppKitVersionNumber10_11 {
67-
appearance = NSVisualEffectMaterial::AppearanceBased;
68-
}
69-
70-
if appearance as u32 > 9 && NSAppKitVersionNumber < NSAppKitVersionNumber10_14 {
71-
appearance = NSVisualEffectMaterial::AppearanceBased;
72-
}
73-
74-
let ns_view: id = window.contentView();
75-
let bounds = NSView::bounds(ns_view);
76-
77-
let blurred_view = NSVisualEffectView::initWithFrame_(NSVisualEffectView::alloc(nil), bounds);
78-
blurred_view.autorelease();
79-
80-
blurred_view.setMaterial_(appearance);
81-
blurred_view.setCornerRadius_(radius.unwrap_or(0.0));
82-
blurred_view.setBlendingMode_(NSVisualEffectBlendingMode::BehindWindow);
83-
blurred_view.setState_(
84-
state
85-
.map(Into::into)
86-
.unwrap_or(NSVisualEffectState::FollowsWindowActiveState),
87-
);
88-
NSVisualEffectView::setAutoresizingMask_(
89-
blurred_view,
90-
NSViewWidthSizable | NSViewHeightSizable,
91-
);
92-
93-
let _: () = msg_send![ns_view, addSubview: blurred_view positioned: NSWindowOrderingMode::NSWindowBelow relativeTo: 0];
94-
}
95-
}
96-
97-
#[allow(non_upper_case_globals)]
98-
const NSAppKitVersionNumber10_14: f64 = 1671.0;
99-
100-
// https://developer.apple.com/documentation/appkit/nsvisualeffectview/blendingmode
101-
#[allow(dead_code)]
102-
#[repr(u64)]
103-
#[derive(Clone, Copy, Debug, PartialEq)]
104-
enum NSVisualEffectBlendingMode {
105-
BehindWindow = 0,
106-
WithinWindow = 1,
107-
}
108-
109-
// macos 10.10+
110-
// https://developer.apple.com/documentation/appkit/nsvisualeffectview
111-
#[allow(non_snake_case)]
112-
trait NSVisualEffectView: Sized {
113-
unsafe fn alloc(_: Self) -> id {
114-
msg_send![class!(NSVisualEffectView), alloc]
115-
}
116-
117-
unsafe fn init(self) -> id;
118-
unsafe fn initWithFrame_(self, frameRect: NSRect) -> id;
119-
unsafe fn bounds(self) -> NSRect;
120-
unsafe fn frame(self) -> NSRect;
121-
unsafe fn setFrameSize(self, frameSize: NSSize);
122-
unsafe fn setFrameOrigin(self, frameOrigin: NSPoint);
123-
124-
unsafe fn superview(self) -> id;
125-
unsafe fn removeFromSuperview(self);
126-
unsafe fn setAutoresizingMask_(self, autoresizingMask: NSAutoresizingMaskOptions);
127-
128-
// API_AVAILABLE(macos(10.12));
129-
unsafe fn isEmphasized(self) -> BOOL;
130-
// API_AVAILABLE(macos(10.12));
131-
unsafe fn setEmphasized_(self, emphasized: BOOL);
132-
133-
unsafe fn setMaterial_(self, material: NSVisualEffectMaterial);
134-
unsafe fn setCornerRadius_(self, radius: f64);
135-
unsafe fn setState_(self, state: NSVisualEffectState);
136-
unsafe fn setBlendingMode_(self, mode: NSVisualEffectBlendingMode);
137-
}
138-
139-
#[allow(non_snake_case)]
140-
impl NSVisualEffectView for id {
141-
unsafe fn init(self) -> id {
142-
msg_send![self, init]
143-
}
144-
145-
unsafe fn initWithFrame_(self, frameRect: NSRect) -> id {
146-
msg_send![self, initWithFrame: frameRect]
147-
}
148-
149-
unsafe fn bounds(self) -> NSRect {
150-
msg_send![self, bounds]
151-
}
152-
153-
unsafe fn frame(self) -> NSRect {
154-
msg_send![self, frame]
155-
}
156-
157-
unsafe fn setFrameSize(self, frameSize: NSSize) {
158-
msg_send![self, setFrameSize: frameSize]
159-
}
160-
161-
unsafe fn setFrameOrigin(self, frameOrigin: NSPoint) {
162-
msg_send![self, setFrameOrigin: frameOrigin]
163-
}
164-
165-
unsafe fn superview(self) -> id {
166-
msg_send![self, superview]
167-
}
168-
169-
unsafe fn removeFromSuperview(self) {
170-
msg_send![self, removeFromSuperview]
171-
}
172-
173-
unsafe fn setAutoresizingMask_(self, autoresizingMask: NSAutoresizingMaskOptions) {
174-
msg_send![self, setAutoresizingMask: autoresizingMask]
175-
}
176-
177-
// API_AVAILABLE(macos(10.12));
178-
unsafe fn isEmphasized(self) -> BOOL {
179-
msg_send![self, isEmphasized]
180-
}
181-
182-
// API_AVAILABLE(macos(10.12));
183-
unsafe fn setEmphasized_(self, emphasized: BOOL) {
184-
msg_send![self, setEmphasized: emphasized]
185-
}
186-
187-
unsafe fn setMaterial_(self, material: NSVisualEffectMaterial) {
188-
msg_send![self, setMaterial: material]
189-
}
190-
191-
unsafe fn setCornerRadius_(self, radius: f64) {
192-
msg_send![self, setCornerRadius: radius]
193-
}
194-
195-
unsafe fn setState_(self, state: NSVisualEffectState) {
196-
msg_send![self, setState: state]
197-
}
198-
199-
unsafe fn setBlendingMode_(self, mode: NSVisualEffectBlendingMode) {
200-
msg_send![self, setBlendingMode: mode]
201-
}
202-
}
203-
204-
/// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/material>
205-
#[repr(u64)]
206-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
207-
pub enum NSVisualEffectMaterial {
208-
#[deprecated = "Since macOS 10.14 a default material appropriate for the view's effectiveAppearance. You should instead choose an appropriate semantic material."]
209-
AppearanceBased = 0,
210-
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
211-
Light = 1,
212-
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
213-
Dark = 2,
214-
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
215-
MediumLight = 8,
216-
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
217-
UltraDark = 9,
218-
219-
/// macOS 10.10+
220-
Titlebar = 3,
221-
/// macOS 10.10+
222-
Selection = 4,
223-
224-
/// macOS 10.11+
225-
Menu = 5,
226-
/// macOS 10.11+
227-
Popover = 6,
228-
/// macOS 10.11+
229-
Sidebar = 7,
230-
231-
/// macOS 10.14+
232-
HeaderView = 10,
233-
/// macOS 10.14+
234-
Sheet = 11,
235-
/// macOS 10.14+
236-
WindowBackground = 12,
237-
/// macOS 10.14+
238-
HudWindow = 13,
239-
/// macOS 10.14+
240-
FullScreenUI = 15,
241-
/// macOS 10.14+
242-
Tooltip = 17,
243-
/// macOS 10.14+
244-
ContentBackground = 18,
245-
/// macOS 10.14+
246-
UnderWindowBackground = 21,
247-
/// macOS 10.14+
248-
UnderPageBackground = 22,
249-
}
250-
251-
/// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>
252-
#[allow(dead_code)]
253-
#[repr(u64)]
254-
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
255-
pub enum NSVisualEffectState {
256-
/// Make window vibrancy state follow the window's active state
257-
FollowsWindowActiveState = 0,
258-
/// Make window vibrancy state always active
259-
Active = 1,
260-
/// Make window vibrancy state always inactive
261-
Inactive = 2,
262-
}
263-
264-
impl From<crate::window::Effect> for NSVisualEffectMaterial {
265-
fn from(value: crate::window::Effect) -> Self {
266-
match value {
49+
window_vibrancy::apply_vibrancy(
50+
window,
51+
match effect {
26752
Effect::AppearanceBased => NSVisualEffectMaterial::AppearanceBased,
26853
Effect::Light => NSVisualEffectMaterial::Light,
26954
Effect::Dark => NSVisualEffectMaterial::Dark,
@@ -284,16 +69,12 @@ impl From<crate::window::Effect> for NSVisualEffectMaterial {
28469
Effect::UnderWindowBackground => NSVisualEffectMaterial::UnderWindowBackground,
28570
Effect::UnderPageBackground => NSVisualEffectMaterial::UnderPageBackground,
28671
_ => unreachable!(),
287-
}
288-
}
289-
}
290-
291-
impl From<crate::window::EffectState> for NSVisualEffectState {
292-
fn from(value: crate::window::EffectState) -> Self {
293-
match value {
72+
},
73+
state.map(|s| match s {
29474
EffectState::FollowsWindowActiveState => NSVisualEffectState::FollowsWindowActiveState,
29575
EffectState::Active => NSVisualEffectState::Active,
29676
EffectState::Inactive => NSVisualEffectState::Inactive,
297-
}
298-
}
77+
}),
78+
radius,
79+
);
29980
}

core/tauri/src/vibrancy/mod.rs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,12 @@ pub fn set_window_effects<R: Runtime>(
1919
) -> crate::Result<()> {
2020
if let Some(_effects) = effects {
2121
#[cfg(windows)]
22-
{
23-
let hwnd = window.hwnd()?;
24-
windows::apply_effects(hwnd, _effects);
25-
}
22+
windows::apply_effects(window, _effects);
2623
#[cfg(target_os = "macos")]
27-
{
28-
let ns_window = window.ns_window()?;
29-
macos::apply_effects(ns_window as _, _effects);
30-
}
24+
macos::apply_effects(window, _effects);
3125
} else {
3226
#[cfg(windows)]
33-
{
34-
let hwnd = window.hwnd()?;
35-
windows::clear_effects(hwnd);
36-
}
27+
windows::clear_effects(window);
3728
}
3829
Ok(())
3930
}

0 commit comments

Comments
 (0)