diff --git a/examples/deployqt.cmake b/examples/deployqt.cmake index de6215c..14f6625 100644 --- a/examples/deployqt.cmake +++ b/examples/deployqt.cmake @@ -55,12 +55,19 @@ function(deploy_qt_libraries arg_target) --no-virtualkeyboard --no-compiler-runtime --no-opengl-sw + --verbose 0 ${__old_deploy_params} "$" ) elseif(APPLE) add_custom_command(TARGET ${arg_target} POST_BUILD COMMAND - "${QT_DEPLOY_EXECUTABLE}" "$" + "${QT_DEPLOY_EXECUTABLE}" + "$" + -qmldir="$" + -qmlimport="${PROJECT_BINARY_DIR}/qml" + -verbose=0 ) + elseif(UNIX) + # TODO endif() endfunction() diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c417051..f55df6a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/utils_mac.mm b/src/core/utils_mac.mm index 6b0ed5f..bd49656 100644 --- a/src/core/utils_mac.mm +++ b/src/core/utils_mac.mm @@ -23,6 +23,7 @@ */ #include "utils.h" +#include "framelessmanager.h" #include #include #include @@ -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(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 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" diff --git a/src/quick/CMakeLists.txt b/src/quick/CMakeLists.txt index 0cac400..1d2ad25 100644 --- a/src/quick/CMakeLists.txt +++ b/src/quick/CMakeLists.txt @@ -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()