diff --git a/.changes/clear-vibrancy.md b/.changes/clear-vibrancy.md new file mode 100644 index 0000000..295e3ea --- /dev/null +++ b/.changes/clear-vibrancy.md @@ -0,0 +1,5 @@ +--- +"window-vibrancy": "patch:enhance" +--- + +Add `clear_vibrancy` function on macOS. \ No newline at end of file diff --git a/.gitignore b/.gitignore index b128202..64e8715 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ /target Cargo.lock +.idea/ +*.iml +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 6fa85ab..d136400 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,12 @@ For a more complete example of usage with [tauri](https://tauri.app/), see [`exa ## Available functions -| Function | Supported platforms | Notes | -| :--- | :---: | :--- | -| `apply_blur`&`clear_blur` | Windows 7/10/11 (22H1 only) | Bad performance when resizing/dragging the window on Windows 11 build 22621+. | -| `apply_acrylic`&`clear_acrylic` | Windows 10/11 | Bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000. | -| `apply_mica`&`clear_mica` | Windows 11 | | -| `apply_vibrancy` | macOS 10.10 and newer | | +| Function | Supported platforms | Notes | +|:----------------------------------|:----------------------------:|:---------------------------------------------------------------------------------------------------| +| `apply_blur`&`clear_blur` | Windows 7/10/11 (22H1 only) | Bad performance when resizing/dragging the window on Windows 11 build 22621+. | +| `apply_acrylic`&`clear_acrylic` | Windows 10/11 | Bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000. | +| `apply_mica`&`clear_mica` | Windows 11 | | +| `apply_vibrancy`&`clear_vibrancy` | macOS 10.10 and newer | | ## Screenshots diff --git a/src/lib.rs b/src/lib.rs index fde3cbf..8329e7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,9 @@ mod windows; pub use macos::{NSVisualEffectMaterial, NSVisualEffectState}; +#[cfg(target_os = "macos")] +pub use macos::NSVisualEffectViewTagged; + /// a tuple of RGBA colors. Each value has minimum of 0 and maximum of 255. pub type Color = (u8, u8, u8, u8); @@ -229,6 +232,28 @@ pub fn apply_vibrancy( } } +/// Clears vibrancy effect applied to window. Works only on macOS 10.10 or newer. +/// +/// ## Platform-specific +/// +/// - **Linux / Windows**: Unsupported. +/// +/// # Returns +/// +/// - `Ok(true)` if the vibrancy effect was cleared +/// - `Ok(false)` if the vibrancy effect was not previously applied by this crate. +pub fn clear_vibrancy(window: impl raw_window_handle::HasWindowHandle) -> Result { + match window.window_handle()?.as_raw() { + #[cfg(target_os = "macos")] + raw_window_handle::RawWindowHandle::AppKit(handle) => unsafe { + macos::clear_vibrancy(handle.ns_view) + }, + _ => Err(Error::UnsupportedPlatform( + "\"clear_vibrancy()\" is only supported on macOS.", + )), + } +} + #[derive(Debug)] pub enum Error { UnsupportedPlatform(&'static str), diff --git a/src/macos.rs b/src/macos.rs deleted file mode 100644 index 83ba102..0000000 --- a/src/macos.rs +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2019-2022 Tauri Programme within The Commons Conservancy -// SPDX-License-Identifier: Apache-2.0 -// SPDX-License-Identifier: MIT - -// The use of NSVisualEffectView comes from https://github.com/joboet/winit/tree/macos_blurred_background -// with a bit of rewrite by @youngsing to make it more like cocoa::appkit style. - -/// -#[repr(u64)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum NSVisualEffectMaterial { - #[deprecated( - since = "macOS 10.14", - note = "A default material appropriate for the view's effectiveAppearance. You should instead choose an appropriate semantic material." - )] - AppearanceBased = 0, - #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] - Light = 1, - #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] - Dark = 2, - #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] - MediumLight = 8, - #[deprecated(since = "macOS 10.14", note = "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, -} - -/// -#[allow(dead_code)] -#[repr(u64)] -#[derive(Clone, Copy, Debug, PartialEq)] -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, -} - -#[cfg(target_os = "macos")] -pub use internal::apply_vibrancy; - -#[cfg(target_os = "macos")] -mod internal { - use std::{ffi::c_void, ptr::NonNull}; - - use objc2_app_kit::{ - NSAppKitVersionNumber, NSAppKitVersionNumber10_10, NSAppKitVersionNumber10_11, - NSAppKitVersionNumber10_14, NSAutoresizingMaskOptions, NSView, NSVisualEffectBlendingMode, - NSVisualEffectMaterial, NSVisualEffectState, NSVisualEffectView, NSWindowOrderingMode, - }; - use objc2_foundation::{CGFloat, MainThreadMarker}; - - use crate::Error; - - #[allow(deprecated)] - pub unsafe fn apply_vibrancy( - ns_view: NonNull, - appearance: super::NSVisualEffectMaterial, - state: Option, - radius: Option, - ) -> Result<(), Error> { - let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread( - "\"apply_vibrancy()\" can only be used on the main thread.", - ))?; - - unsafe { - let view: &NSView = ns_view.cast().as_ref(); - - if NSAppKitVersionNumber < NSAppKitVersionNumber10_10 { - return Err(Error::UnsupportedPlatformVersion( - "\"apply_vibrancy()\" is only available on macOS 10.0 or newer.", - )); - } - - let mut m = NSVisualEffectMaterial(appearance as isize); - if (appearance as u32 > 9 && NSAppKitVersionNumber < NSAppKitVersionNumber10_14) - || (appearance as u32 > 4 && NSAppKitVersionNumber < NSAppKitVersionNumber10_11) - { - m = NSVisualEffectMaterial::AppearanceBased; - } - - let bounds = view.bounds(); - let blurred_view = NSVisualEffectView::initWithFrame(mtm.alloc(), bounds); - - blurred_view.setMaterial(m); - set_corner_radius(&blurred_view, radius.unwrap_or(0.0)); - blurred_view.setBlendingMode(NSVisualEffectBlendingMode::BehindWindow); - blurred_view.setState( - state - .map(|state| NSVisualEffectState(state as isize)) - .unwrap_or(NSVisualEffectState::FollowsWindowActiveState), - ); - blurred_view.setAutoresizingMask( - NSAutoresizingMaskOptions::NSViewWidthSizable - | NSAutoresizingMaskOptions::NSViewHeightSizable, - ); - - view.addSubview_positioned_relativeTo( - &blurred_view, - NSWindowOrderingMode::NSWindowBelow, - None, - ); - } - Ok(()) - } - - // TODO: Does not seem to be public? - fn set_corner_radius(view: &NSVisualEffectView, radius: CGFloat) { - unsafe { objc2::msg_send![view, setCornerRadius: radius] } - } -} diff --git a/src/macos/internal.rs b/src/macos/internal.rs new file mode 100644 index 0000000..d8fb43c --- /dev/null +++ b/src/macos/internal.rs @@ -0,0 +1,79 @@ +use objc2_app_kit::{ + NSAppKitVersionNumber, NSAppKitVersionNumber10_10, NSAppKitVersionNumber10_11, + NSAppKitVersionNumber10_14, NSAutoresizingMaskOptions, NSView, NSVisualEffectBlendingMode, + NSVisualEffectMaterial, NSVisualEffectState, NSWindowOrderingMode, +}; +use objc2_foundation::{MainThreadMarker, NSInteger}; +use std::{ffi::c_void, ptr::NonNull}; + +use crate::macos::NSVisualEffectViewTagged; +use crate::Error; + +/// NSView::tag for NSVisualEffectViewTagged, just a random number +pub const NS_VIEW_TAG_BLUR_VIEW: NSInteger = 91376254; + +#[allow(deprecated)] +pub unsafe fn apply_vibrancy( + ns_view: NonNull, + appearance: super::NSVisualEffectMaterial, + state: Option, + radius: Option, +) -> Result<(), Error> { + let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread( + "\"apply_vibrancy()\" can only be used on the main thread.", + ))?; + + unsafe { + let view: &NSView = ns_view.cast().as_ref(); + + if NSAppKitVersionNumber < NSAppKitVersionNumber10_10 { + return Err(Error::UnsupportedPlatformVersion( + "\"apply_vibrancy()\" is only available on macOS 10.0 or newer.", + )); + } + + let mut m = NSVisualEffectMaterial(appearance as isize); + if (appearance as u32 > 9 && NSAppKitVersionNumber < NSAppKitVersionNumber10_14) + || (appearance as u32 > 4 && NSAppKitVersionNumber < NSAppKitVersionNumber10_11) + { + m = NSVisualEffectMaterial::AppearanceBased; + } + + let bounds = view.bounds(); + let blurred_view = + NSVisualEffectViewTagged::initWithFrame(mtm.alloc(), bounds, NS_VIEW_TAG_BLUR_VIEW); + + blurred_view.setMaterial(m); + blurred_view.setCornerRadius(radius.unwrap_or(0.0)); + blurred_view.setBlendingMode(NSVisualEffectBlendingMode::BehindWindow); + blurred_view.setState( + state + .map(|state| NSVisualEffectState(state as isize)) + .unwrap_or(NSVisualEffectState::FollowsWindowActiveState), + ); + blurred_view.setAutoresizingMask( + NSAutoresizingMaskOptions::NSViewWidthSizable + | NSAutoresizingMaskOptions::NSViewHeightSizable, + ); + + view.addSubview_positioned_relativeTo( + &blurred_view, + NSWindowOrderingMode::NSWindowBelow, + None, + ); + } + + Ok(()) +} + +pub unsafe fn clear_vibrancy(ns_view: NonNull) -> Result { + let view: &NSView = ns_view.cast().as_ref(); + let blurred_view = view.viewWithTag(NS_VIEW_TAG_BLUR_VIEW); + + if let Some(blurred_view) = blurred_view { + blurred_view.removeFromSuperview(); + return Ok(true); + } + + Ok(false) +} diff --git a/src/macos/mod.rs b/src/macos/mod.rs new file mode 100644 index 0000000..b0563ca --- /dev/null +++ b/src/macos/mod.rs @@ -0,0 +1,83 @@ +// Copyright 2019-2022 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +// The use of NSVisualEffectView comes from https://github.com/joboet/winit/tree/macos_blurred_background +// with a bit of rewrite by @youngsing to make it more like cocoa::appkit style. +/// +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum NSVisualEffectMaterial { + #[deprecated( + since = "macOS 10.14", + note = "A default material appropriate for the view's effectiveAppearance. You should instead choose an appropriate semantic material." + )] + AppearanceBased = 0, + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + Light = 1, + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + Dark = 2, + #[deprecated(since = "macOS 10.14", note = "Use a semantic material instead.")] + MediumLight = 8, + #[deprecated(since = "macOS 10.14", note = "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, +} + +/// +#[allow(dead_code)] +#[repr(u64)] +#[derive(Clone, Copy, Debug, PartialEq)] +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, +} + +#[cfg(target_os = "macos")] +mod internal; + +#[cfg(target_os = "macos")] +pub use internal::apply_vibrancy; + +#[cfg(target_os = "macos")] +pub use internal::clear_vibrancy; + +#[cfg(target_os = "macos")] +mod ns_visual_effect_view_tagged; + +#[cfg(target_os = "macos")] +pub use ns_visual_effect_view_tagged::NSVisualEffectViewTagged; diff --git a/src/macos/ns_visual_effect_view_tagged.rs b/src/macos/ns_visual_effect_view_tagged.rs new file mode 100644 index 0000000..151d0d3 --- /dev/null +++ b/src/macos/ns_visual_effect_view_tagged.rs @@ -0,0 +1,112 @@ +use objc2::mutability::MainThreadOnly; +use objc2::rc::{Allocated, Retained}; +use objc2::ClassType; +use objc2::DeclaredClass; +use objc2::{declare_class, msg_send, msg_send_id}; +use objc2_app_kit::{ + NSAutoresizingMaskOptions, NSVisualEffectBlendingMode, NSVisualEffectMaterial, + NSVisualEffectState, NSVisualEffectView, +}; +use objc2_foundation::{CGFloat, NSInteger, NSRect}; + +/// NSVisualEffectViewTagged state. +/// Forced to be public by declare_class! macro. +#[derive(Default, Debug, PartialEq, Eq)] +pub struct NSVisualEffectViewTaggedIvars { + /// NSView tag to identify the view + pub tag: NSInteger, +} + +declare_class!( + /// A custom NSVisualEffectView subclass + /// that overrides the tag method to provide a custom tag, to later identify the view + pub struct NSVisualEffectViewTagged; + + unsafe impl ClassType for NSVisualEffectViewTagged { + type Super = NSVisualEffectView; + type Mutability = MainThreadOnly; + + const NAME: &'static str = "NSVisualEffectViewTagged"; + } + + impl DeclaredClass for NSVisualEffectViewTagged { + type Ivars = NSVisualEffectViewTaggedIvars; + } + + unsafe impl NSVisualEffectViewTagged { + #[method(tag)] + fn tag(&self) -> NSInteger { + self.ivars().tag + } + } +); + +#[allow(non_snake_case)] +impl NSVisualEffectViewTagged { + /// # Safety + /// + /// This method is unsafe because it calls an Objective-C method. + pub unsafe fn initWithFrame( + this: Allocated, + frame_rect: NSRect, + tag: NSInteger, + ) -> Retained { + let state = NSVisualEffectViewTaggedIvars { tag }; + let this = this.set_ivars(state); + + msg_send_id![super(this), initWithFrame: frame_rect] + } + + /// + /// + /// # Safety + /// + /// This method is unsafe because it calls an Objective-C method. + pub unsafe fn setMaterial(&self, material: NSVisualEffectMaterial) { + let () = msg_send![self, setMaterial: material]; + } + + /// + /// + /// # Safety + /// + /// This method is unsafe because it calls an Objective-C method. + pub unsafe fn setBlendingMode(&self, blending_mode: NSVisualEffectBlendingMode) { + let () = msg_send![self, setBlendingMode: blending_mode]; + } + + /// + /// + /// # Safety + /// + /// This method is unsafe because it calls an Objective-C method. + pub unsafe fn setState(&self, state: NSVisualEffectState) { + let () = msg_send![self, setState: state]; + } + + /// NSView inherited method + /// + /// + /// # Safety + /// + /// This method is unsafe because it calls an Objective-C method. + pub unsafe fn setAutoresizingMask(&self, mask: NSAutoresizingMaskOptions) { + let () = msg_send![self, setAutoresizingMask: mask]; + } + + /// TODO: Does not seem to be public? + /// Method is not listed in Apple documentation, might be private, but it works + /// + /// # Safety + /// + /// This method is unsafe because it calls an Objective-C method. + pub unsafe fn setCornerRadius(&self, radius: CGFloat) { + let () = msg_send![self, setCornerRadius: radius]; + + // TODO: consider public & documented approach instead, visual effect is the same + // self.setWantsLayer(true); + // if let Some(layer) = self.layer() { + // layer.setCornerRadius(radius); + // } + } +}