blur behind window: add macos implementation

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-07-09 14:59:47 +08:00
parent 7e3a735a7d
commit 2f36d1f73e
4 changed files with 149 additions and 24 deletions

View File

@ -55,12 +55,19 @@ function(deploy_qt_libraries arg_target)
--no-virtualkeyboard
--no-compiler-runtime
--no-opengl-sw
--verbose 0
${__old_deploy_params}
"$<TARGET_FILE:${arg_target}>"
)
elseif(APPLE)
add_custom_command(TARGET ${arg_target} POST_BUILD COMMAND
"${QT_DEPLOY_EXECUTABLE}" "$<TARGET_BUNDLE_DIR:${arg_target}>"
"${QT_DEPLOY_EXECUTABLE}"
"$<TARGET_BUNDLE_DIR:${arg_target}>"
-qmldir="$<TARGET_PROPERTY:${arg_target},SOURCE_DIR>"
-qmlimport="${PROJECT_BINARY_DIR}/qml"
-verbose=0
)
elseif(UNIX)
# TODO
endif()
endfunction()

View File

@ -142,6 +142,7 @@ if(APPLE)
target_link_libraries(${SUB_PROJ_NAME} PRIVATE
"-framework Foundation"
"-framework Cocoa"
"-framework AppKit"
)
elseif(UNIX)
target_compile_definitions(${SUB_PROJ_NAME} PRIVATE

View File

@ -23,6 +23,7 @@
*/
#include "utils.h"
#include "framelessmanager.h"
#include <QtCore/qdebug.h>
#include <QtCore/qhash.h>
#include <QtCore/qcoreapplication.h>
@ -38,20 +39,23 @@ FRAMELESSHELPER_BEGIN_NAMESPACE
using namespace Global;
class NSWindowProxy
class NSWindowProxy : public QObject
{
Q_OBJECT
Q_DISABLE_COPY_MOVE(NSWindowProxy)
public:
explicit NSWindowProxy(NSWindow *window)
explicit NSWindowProxy(QWindow *qtWindow, NSWindow *macWindow, QObject *parent = nil) : QObject(parent)
{
Q_ASSERT(window);
Q_ASSERT(!instances.contains(window));
if (!window || instances.contains(window)) {
Q_ASSERT(qtWindow);
Q_ASSERT(macWindow);
Q_ASSERT(!instances.contains(macWindow));
if (!qtWindow || !macWindow || instances.contains(macWindow)) {
return;
}
nswindow = window;
instances.insert(nswindow, this);
qwindow = qtWindow;
nswindow = macWindow;
instances.insert(macWindow, this);
saveState();
if (!windowClass) {
windowClass = [nswindow class];
@ -60,7 +64,7 @@ public:
}
}
~NSWindowProxy()
~NSWindowProxy() override
{
instances.remove(nswindow);
if (instances.count() <= 0) {
@ -71,6 +75,7 @@ public:
nswindow = nil;
}
public Q_SLOTS:
void saveState()
{
oldStyleMask = nswindow.styleMask;
@ -180,6 +185,77 @@ public:
[nswindow standardWindowButton:NSWindowZoomButton].hidden = (visible ? NO : YES);
}
void setBlurBehindWindowEnabled(const bool enable)
{
if (enable) {
if (blurEffect) {
return;
}
NSView * const view = [nswindow contentView];
#if 1
const Class visualEffectViewClass = NSClassFromString(@"NSVisualEffectView");
if (!visualEffectViewClass) {
return;
}
NSVisualEffectView * const blurView = [[visualEffectViewClass alloc] initWithFrame:view.bounds];
#else
NSVisualEffectView * const blurView = [[NSVisualEffectView alloc] initWithFrame:view.bounds];
#endif
blurView.material = NSVisualEffectMaterialUnderWindowBackground;
blurView.blendingMode = NSVisualEffectBlendingModeBehindWindow;
blurView.state = NSVisualEffectStateFollowsWindowActiveState;
const NSView * const parent = [view superview];
[parent addSubview:blurView positioned:NSWindowBelow relativeTo:view];
blurEffect = blurView;
updateBlurTheme();
updateBlurSize();
connect(FramelessManager::instance(),
&FramelessManager::systemThemeChanged, this, &NSWindowProxy::updateBlurTheme);
connect(qwindow, &QWindow::widthChanged, this, &NSWindowProxy::updateBlurSize);
connect(qwindow, &QWindow::heightChanged, this, &NSWindowProxy::updateBlurSize);
} else {
if (!blurEffect) {
return;
}
if (widthChangeConnection) {
disconnect(widthChangeConnection);
widthChangeConnection = {};
}
if (heightChangeConnection) {
disconnect(heightChangeConnection);
heightChangeConnection = {};
}
if (themeChangeConnection) {
disconnect(themeChangeConnection);
themeChangeConnection = {};
}
[blurEffect removeFromSuperview];
blurEffect = nil;
}
}
void updateBlurSize()
{
if (!blurEffect) {
return;
}
const NSView * const view = [nswindow contentView];
blurEffect.frame = view.frame;
}
void updateBlurTheme()
{
if (!blurEffect) {
return;
}
const auto view = static_cast<NSVisualEffectView *>(blurEffect);
if (Utils::shouldAppsUseDarkMode()) {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantDark"];
} else {
view.appearance = [NSAppearance appearanceNamed:@"NSAppearanceNameVibrantLight"];
}
}
private:
static BOOL canBecomeKeyWindow(id obj, SEL sel)
{
@ -249,8 +325,10 @@ private:
}
private:
QWindow *qwindow = nil;
NSWindow *nswindow = nil;
NSEvent *lastMouseDownEvent = nil;
NSView *blurEffect = nil;
NSWindowStyleMask oldStyleMask = 0;
BOOL oldTitlebarAppearsTransparent = NO;
@ -263,6 +341,10 @@ private:
BOOL oldZoomButtonVisible = NO;
NSWindowTitleVisibility oldTitleVisibility = NSWindowTitleVisible;
QMetaObject::Connection widthChangeConnection = {};
QMetaObject::Connection heightChangeConnection = {};
QMetaObject::Connection themeChangeConnection = {};
static inline QHash<NSWindow *, NSWindowProxy *> instances = {};
static inline Class windowClass = nil;
@ -300,6 +382,29 @@ Q_GLOBAL_STATIC(NSWindowProxyHash, g_nswindowOverrideHash);
return [nsview window];
}
[[nodiscard]] static inline NSWindowProxy *ensureWindowProxy(const WId windowId)
{
Q_ASSERT(windowId);
if (!windowId) {
return nil;
}
if (!g_nswindowOverrideHash()->contains(windowId)) {
QWindow * const qwindow = Utils::findWindow(windowId);
Q_ASSERT(qwindow);
if (!qwindow) {
return nil;
}
NSWindow * const nswindow = mac_getNSWindow(windowId);
Q_ASSERT(nswindow);
if (!nswindow) {
return nil;
}
const auto proxy = new NSWindowProxy(qwindow, nswindow);
g_nswindowOverrideHash()->insert(windowId, proxy);
}
return g_nswindowOverrideHash()->value(windowId);
}
SystemTheme Utils::getSystemTheme()
{
// ### TODO: how to detect high contrast mode on macOS?
@ -312,20 +417,7 @@ void Utils::setSystemTitleBarVisible(const WId windowId, const bool visible)
if (!windowId) {
return;
}
if (!g_nswindowOverrideHash()->contains(windowId)) {
NSWindow * const nswindow = mac_getNSWindow(windowId);
Q_ASSERT(nswindow);
if (!nswindow) {
return;
}
const auto proxy = new NSWindowProxy(nswindow);
g_nswindowOverrideHash()->insert(windowId, proxy);
}
NSWindowProxy * const proxy = g_nswindowOverrideHash()->value(windowId);
Q_ASSERT(proxy);
if (!proxy) {
return;
}
NSWindowProxy * const proxy = ensureWindowProxy(windowId);
proxy->setSystemTitleBarVisible(visible);
}
@ -398,4 +490,25 @@ bool Utils::shouldAppsUseDarkMode_macos()
return false;
}
bool Utils::setBlurBehindWindowEnabled(const WId windowId, const BlurMode mode, const QColor &color)
{
Q_UNUSED(color);
Q_ASSERT(windowId);
if (!windowId) {
return false;
}
const BlurMode blurMode = [mode]() -> BlurMode {
if ((mode == BlurMode::Disable) || (mode == BlurMode::Default)) {
return mode;
}
qWarning() << "The BlurMode::Windows_* enum values are not supported on macOS.";
return BlurMode::Default;
}();
NSWindowProxy * const proxy = ensureWindowProxy(windowId);
proxy->setBlurBehindWindowEnabled(blurMode == BlurMode::Default);
return true;
}
FRAMELESSHELPER_END_NAMESPACE
#include "utils_mac.moc"

View File

@ -95,6 +95,10 @@ if(${QT_VERSION} VERSION_GREATER_EQUAL 6.2)
QtQuick
QtQuick.Controls.Basic
)
set(__lib_prefix)
if(UNIX)
set(__lib_prefix lib)
endif()
set(__lib_suffix)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(__lib_suffix ${CMAKE_DEBUG_POSTFIX})
@ -110,7 +114,7 @@ if(${QT_VERSION} VERSION_GREATER_EQUAL 6.2)
install(FILES
"${__import_dir}/qmldir"
"${__import_dir}/${SUB_PROJ_NAME}.qmltypes"
"${__import_dir}/${SUB_PROJ_NAME}plugin${__lib_suffix}.${__lib_ext}"
"${__import_dir}/${__lib_prefix}${SUB_PROJ_NAME}plugin${__lib_suffix}.${__lib_ext}"
DESTINATION ${CMAKE_INSTALL_PREFIX}/qml/${__import_uri}
)
endif()