Skip to content

Commit

Permalink
feat(windows): add tabbed effect (#7794)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas Nogueira <[email protected]>
Co-authored-by: Lucas Nogueira <[email protected]>
  • Loading branch information
3 people authored Oct 17, 2023
1 parent 880266a commit ed32257
Show file tree
Hide file tree
Showing 17 changed files with 161 additions and 501 deletions.
5 changes: 5 additions & 0 deletions .changes/api-tabbed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tauri-apps/api": "patch:feat"
---

On Windows, add `Effect.Tabbed`,`Effect.TabbedDark` and `Effect.TabbedLight` effects.
5 changes: 5 additions & 0 deletions .changes/tauri-tabbed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri": "patch:feat"
---

On Windows, add `Effect::Tabbed`,`Effect::TabbedDark` and `Effect::TabbedLight` effects.
6 changes: 6 additions & 0 deletions .changes/tauri-utils-tabbed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"tauri-utils": "patch:feat"
---

On Windows, add `WindowEffect::Tabbed`,`WindowEffect::TabbedDark` and `WindowEffect::TabbedLight`

21 changes: 21 additions & 0 deletions core/tauri-config-schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,27 @@
"micaLight"
]
},
{
"description": "Tabbed effect that matches the system dark perefence **Windows 11 Only**",
"type": "string",
"enum": [
"tabbed"
]
},
{
"description": "Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**",
"type": "string",
"enum": [
"tabbedDark"
]
},
{
"description": "Tabbed effect with light mode **Windows 11 Only**",
"type": "string",
"enum": [
"tabbedLight"
]
},
{
"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.",
"type": "string",
Expand Down
9 changes: 9 additions & 0 deletions core/tauri-utils/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,12 @@ pub struct BundleConfig {
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Color(pub u8, pub u8, pub u8, pub u8);

impl From<Color> for (u8, u8, u8, u8) {
fn from(value: Color) -> Self {
(value.0, value.1, value.2, value.3)
}
}

/// The window effects configuration object
#[skip_serializing_none]
#[derive(Debug, PartialEq, Clone, Deserialize, Serialize, Default)]
Expand Down Expand Up @@ -2197,6 +2203,9 @@ mod build {
WindowEffect::MicaLight => quote! { #prefix::MicaLight},
WindowEffect::Blur => quote! { #prefix::Blur},
WindowEffect::Acrylic => quote! { #prefix::Acrylic},
WindowEffect::Tabbed => quote! { #prefix::Tabbed },
WindowEffect::TabbedDark => quote! { #prefix::TabbedDark },
WindowEffect::TabbedLight => quote! { #prefix::TabbedLight },
})
}
}
Expand Down
6 changes: 6 additions & 0 deletions core/tauri-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ mod window_effects {
MicaDark,
/// Mica effect with light mode **Windows 11 Only**
MicaLight,
/// Tabbed effect that matches the system dark perefence **Windows 11 Only**
Tabbed,
/// Tabbed effect with dark mode but only if dark mode is enabled on the system **Windows 11 Only**
TabbedDark,
/// Tabbed effect with light mode **Windows 11 Only**
TabbedLight,
/// **Windows 7/10/11(22H1) Only**
///
/// ## Notes
Expand Down
1 change: 1 addition & 0 deletions core/tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ infer = { version = "0.15", optional = true }
png = { version = "0.17", optional = true }
ico = { version = "0.3.0", optional = true }
http-range = { version = "0.1.4", optional = true }
window-vibrancy = "0.4"

[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]
muda = { version = "0.8", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion core/tauri/scripts/bundle.global.js

Large diffs are not rendered by default.

245 changes: 13 additions & 232 deletions core/tauri/src/vibrancy/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,17 @@

use crate::utils::config::WindowEffectsConfig;
use crate::window::{Effect, EffectState};
use cocoa::{
appkit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_10, NSAppKitVersionNumber10_11,
NSAutoresizingMaskOptions, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow,
NSWindowOrderingMode,
},
base::{id, nil, BOOL},
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize},
};
use objc::{class, msg_send, sel, sel_impl};
use raw_window_handle::HasRawWindowHandle;
use window_vibrancy::{NSVisualEffectMaterial, NSVisualEffectState};

pub fn apply_effects(window: id, effects: WindowEffectsConfig) {
pub fn apply_effects(window: impl HasRawWindowHandle, effects: WindowEffectsConfig) {
let WindowEffectsConfig {
effects,
radius,
state,
..
} = effects;
let mut appearance: NSVisualEffectMaterial = if let Some(effect) = effects.into_iter().find(|e| {
let effect = if let Some(effect) = effects.into_iter().find(|e| {
matches!(
e,
Effect::AppearanceBased
Expand All @@ -49,221 +41,14 @@ pub fn apply_effects(window: id, effects: WindowEffectsConfig) {
| Effect::UnderPageBackground
)
}) {
effect.into()
effect
} else {
return;
};

unsafe {
if NSAppKitVersionNumber < NSAppKitVersionNumber10_10 {
return;
}

if !msg_send![class!(NSThread), isMainThread] {
return;
}

if appearance as u32 > 4 && NSAppKitVersionNumber < NSAppKitVersionNumber10_11 {
appearance = NSVisualEffectMaterial::AppearanceBased;
}

if appearance as u32 > 9 && NSAppKitVersionNumber < NSAppKitVersionNumber10_14 {
appearance = NSVisualEffectMaterial::AppearanceBased;
}

let ns_view: id = window.contentView();
let bounds = NSView::bounds(ns_view);

let blurred_view = NSVisualEffectView::initWithFrame_(NSVisualEffectView::alloc(nil), bounds);
blurred_view.autorelease();

blurred_view.setMaterial_(appearance);
blurred_view.setCornerRadius_(radius.unwrap_or(0.0));
blurred_view.setBlendingMode_(NSVisualEffectBlendingMode::BehindWindow);
blurred_view.setState_(
state
.map(Into::into)
.unwrap_or(NSVisualEffectState::FollowsWindowActiveState),
);
NSVisualEffectView::setAutoresizingMask_(
blurred_view,
NSViewWidthSizable | NSViewHeightSizable,
);

let _: () = msg_send![ns_view, addSubview: blurred_view positioned: NSWindowOrderingMode::NSWindowBelow relativeTo: 0];
}
}

#[allow(non_upper_case_globals)]
const NSAppKitVersionNumber10_14: f64 = 1671.0;

// https://developer.apple.com/documentation/appkit/nsvisualeffectview/blendingmode
#[allow(dead_code)]
#[repr(u64)]
#[derive(Clone, Copy, Debug, PartialEq)]
enum NSVisualEffectBlendingMode {
BehindWindow = 0,
WithinWindow = 1,
}

// macos 10.10+
// https://developer.apple.com/documentation/appkit/nsvisualeffectview
#[allow(non_snake_case)]
trait NSVisualEffectView: Sized {
unsafe fn alloc(_: Self) -> id {
msg_send![class!(NSVisualEffectView), alloc]
}

unsafe fn init(self) -> id;
unsafe fn initWithFrame_(self, frameRect: NSRect) -> id;
unsafe fn bounds(self) -> NSRect;
unsafe fn frame(self) -> NSRect;
unsafe fn setFrameSize(self, frameSize: NSSize);
unsafe fn setFrameOrigin(self, frameOrigin: NSPoint);

unsafe fn superview(self) -> id;
unsafe fn removeFromSuperview(self);
unsafe fn setAutoresizingMask_(self, autoresizingMask: NSAutoresizingMaskOptions);

// API_AVAILABLE(macos(10.12));
unsafe fn isEmphasized(self) -> BOOL;
// API_AVAILABLE(macos(10.12));
unsafe fn setEmphasized_(self, emphasized: BOOL);

unsafe fn setMaterial_(self, material: NSVisualEffectMaterial);
unsafe fn setCornerRadius_(self, radius: f64);
unsafe fn setState_(self, state: NSVisualEffectState);
unsafe fn setBlendingMode_(self, mode: NSVisualEffectBlendingMode);
}

#[allow(non_snake_case)]
impl NSVisualEffectView for id {
unsafe fn init(self) -> id {
msg_send![self, init]
}

unsafe fn initWithFrame_(self, frameRect: NSRect) -> id {
msg_send![self, initWithFrame: frameRect]
}

unsafe fn bounds(self) -> NSRect {
msg_send![self, bounds]
}

unsafe fn frame(self) -> NSRect {
msg_send![self, frame]
}

unsafe fn setFrameSize(self, frameSize: NSSize) {
msg_send![self, setFrameSize: frameSize]
}

unsafe fn setFrameOrigin(self, frameOrigin: NSPoint) {
msg_send![self, setFrameOrigin: frameOrigin]
}

unsafe fn superview(self) -> id {
msg_send![self, superview]
}

unsafe fn removeFromSuperview(self) {
msg_send![self, removeFromSuperview]
}

unsafe fn setAutoresizingMask_(self, autoresizingMask: NSAutoresizingMaskOptions) {
msg_send![self, setAutoresizingMask: autoresizingMask]
}

// API_AVAILABLE(macos(10.12));
unsafe fn isEmphasized(self) -> BOOL {
msg_send![self, isEmphasized]
}

// API_AVAILABLE(macos(10.12));
unsafe fn setEmphasized_(self, emphasized: BOOL) {
msg_send![self, setEmphasized: emphasized]
}

unsafe fn setMaterial_(self, material: NSVisualEffectMaterial) {
msg_send![self, setMaterial: material]
}

unsafe fn setCornerRadius_(self, radius: f64) {
msg_send![self, setCornerRadius: radius]
}

unsafe fn setState_(self, state: NSVisualEffectState) {
msg_send![self, setState: state]
}

unsafe fn setBlendingMode_(self, mode: NSVisualEffectBlendingMode) {
msg_send![self, setBlendingMode: mode]
}
}

/// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/material>
#[repr(u64)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NSVisualEffectMaterial {
#[deprecated = "Since macOS 10.14 a default material appropriate for the view's effectiveAppearance. You should instead choose an appropriate semantic material."]
AppearanceBased = 0,
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
Light = 1,
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
Dark = 2,
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
MediumLight = 8,
#[deprecated = "Since macOS 10.14 use a semantic material instead."]
UltraDark = 9,

/// macOS 10.10+
Titlebar = 3,
/// macOS 10.10+
Selection = 4,

/// macOS 10.11+
Menu = 5,
/// macOS 10.11+
Popover = 6,
/// macOS 10.11+
Sidebar = 7,

/// macOS 10.14+
HeaderView = 10,
/// macOS 10.14+
Sheet = 11,
/// macOS 10.14+
WindowBackground = 12,
/// macOS 10.14+
HudWindow = 13,
/// macOS 10.14+
FullScreenUI = 15,
/// macOS 10.14+
Tooltip = 17,
/// macOS 10.14+
ContentBackground = 18,
/// macOS 10.14+
UnderWindowBackground = 21,
/// macOS 10.14+
UnderPageBackground = 22,
}

/// <https://developer.apple.com/documentation/appkit/nsvisualeffectview/state>
#[allow(dead_code)]
#[repr(u64)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NSVisualEffectState {
/// Make window vibrancy state follow the window's active state
FollowsWindowActiveState = 0,
/// Make window vibrancy state always active
Active = 1,
/// Make window vibrancy state always inactive
Inactive = 2,
}

impl From<crate::window::Effect> for NSVisualEffectMaterial {
fn from(value: crate::window::Effect) -> Self {
match value {
window_vibrancy::apply_vibrancy(
window,
match effect {
Effect::AppearanceBased => NSVisualEffectMaterial::AppearanceBased,
Effect::Light => NSVisualEffectMaterial::Light,
Effect::Dark => NSVisualEffectMaterial::Dark,
Expand All @@ -284,16 +69,12 @@ impl From<crate::window::Effect> for NSVisualEffectMaterial {
Effect::UnderWindowBackground => NSVisualEffectMaterial::UnderWindowBackground,
Effect::UnderPageBackground => NSVisualEffectMaterial::UnderPageBackground,
_ => unreachable!(),
}
}
}

impl From<crate::window::EffectState> for NSVisualEffectState {
fn from(value: crate::window::EffectState) -> Self {
match value {
},
state.map(|s| match s {
EffectState::FollowsWindowActiveState => NSVisualEffectState::FollowsWindowActiveState,
EffectState::Active => NSVisualEffectState::Active,
EffectState::Inactive => NSVisualEffectState::Inactive,
}
}
}),
radius,
);
}
15 changes: 3 additions & 12 deletions core/tauri/src/vibrancy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,12 @@ pub fn set_window_effects<R: Runtime>(
) -> crate::Result<()> {
if let Some(_effects) = effects {
#[cfg(windows)]
{
let hwnd = window.hwnd()?;
windows::apply_effects(hwnd, _effects);
}
windows::apply_effects(window, _effects);
#[cfg(target_os = "macos")]
{
let ns_window = window.ns_window()?;
macos::apply_effects(ns_window as _, _effects);
}
macos::apply_effects(window, _effects);
} else {
#[cfg(windows)]
{
let hwnd = window.hwnd()?;
windows::clear_effects(hwnd);
}
windows::clear_effects(window);
}
Ok(())
}
Loading

0 comments on commit ed32257

Please sign in to comment.