some wip code

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-04-05 19:54:43 +08:00
parent b5d2ae5888
commit e19dad5b82
24 changed files with 420 additions and 153 deletions

View File

@ -3,6 +3,7 @@
## Highlights compared to 1.x
- Windows: Gained the ability to only remove the title bar but preserve the window frame at the same time.
- Windows: Support the maximize button docking feature introduced in Windows 11.
- Windows: The flicker and jitter during window resizing is completely gone.
- Windows: The system menu will be opened if you right-click on your custom title bar.
- Windows: Replaced Qt's original system menu with FramelessHelper's homemade one, which looks a lot better than the original one.

View File

@ -39,7 +39,7 @@ public:
static void addWindow(const Global::UserSettings &settings, const Global::SystemParameters &params);
Q_NODISCARD bool nativeEventFilter(const QByteArray &eventType, void *message, NATIVE_EVENT_RESULT_TYPE *result) override;
Q_NODISCARD bool nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result) override;
};
FRAMELESSHELPER_END_NAMESPACE

View File

@ -71,6 +71,7 @@
#endif
#include <windows.h>
#include <uxtheme.h>
#include <shellapi.h>
#include <dwmapi.h>
@ -188,10 +189,12 @@ GetDpiForMonitor(
[[maybe_unused]] static constexpr const int kAutoHideTaskBarThickness = 2; // The thickness of an auto-hide taskbar in pixels.
[[maybe_unused]] static constexpr const char kDwmRegistryKey[] = R"(Software\Microsoft\Windows\DWM)";
[[maybe_unused]] static constexpr const char kPersonalizeRegistryKey[] = R"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)";
[[maybe_unused]] static constexpr const char kThemeSettingChangeEventName[] = "ImmersiveColorSet";
[[maybe_unused]] static constexpr const char kDwmColorKeyName[] = "ColorPrevalence";
[[maybe_unused]] static constexpr const wchar_t kDwmRegistryKey[] = LR"(Software\Microsoft\Windows\DWM)";
[[maybe_unused]] static constexpr const wchar_t kPersonalizeRegistryKey[] = LR"(Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)";
[[maybe_unused]] static constexpr const wchar_t kThemeSettingChangeEventName[] = L"ImmersiveColorSet";
[[maybe_unused]] static constexpr const wchar_t kDwmColorKeyName[] = L"ColorPrevalence";
[[maybe_unused]] static constexpr const wchar_t kSystemDarkThemeResourceName[] = L"DarkMode_Explorer";
[[maybe_unused]] static constexpr const wchar_t kSystemLightThemeResourceName[] = L"Explorer";
[[maybe_unused]] static constexpr const DWORD _DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
[[maybe_unused]] static constexpr const DWORD _DWMWA_USE_IMMERSIVE_DARK_MODE = 20;

View File

@ -31,6 +31,7 @@
#include <QtCore/qpointer.h>
#include <QtGui/qcolor.h>
#include <QtGui/qwindowdefs.h>
#include <functional>
QT_BEGIN_NAMESPACE
class QScreen;
@ -80,10 +81,12 @@ QT_END_NAMESPACE
# define Q_NODISCARD
#endif
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
using NATIVE_EVENT_RESULT_TYPE = qintptr;
#else
using NATIVE_EVENT_RESULT_TYPE = long;
#ifndef QT_NATIVE_EVENT_RESULT_TYPE
# if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
# define QT_NATIVE_EVENT_RESULT_TYPE qintptr
# else
# define QT_NATIVE_EVENT_RESULT_TYPE long
# endif
#endif
#ifndef QUtf8String
@ -158,16 +161,15 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API)
[[maybe_unused]] static constexpr const QColor kDefaultBlackColor = {0, 0, 0}; // #000000
[[maybe_unused]] static constexpr const QColor kDefaultWhiteColor = {255, 255, 255}; // #FFFFFF
[[maybe_unused]] static constexpr const QColor kDefaultTransparentColor = {0, 0, 0, 0};
[[maybe_unused]] static constexpr const QColor kDefaultDarkGrayColor = {169, 169, 169}; // #A9A9A9
[[maybe_unused]] static constexpr const QColor kDefaultSystemLightColor = {240, 240, 240}; // #F0F0F0
[[maybe_unused]] static constexpr const QColor kDefaultSystemDarkColor = {32, 32, 32}; // #202020
[[maybe_unused]] static constexpr const QColor kDefaultFrameBorderActiveColor = {77, 77, 77}; // #4D4D4D
[[maybe_unused]] static constexpr const QColor kDefaultFrameBorderInactiveColorDark = {87, 89, 89}; // #575959
[[maybe_unused]] static constexpr const QColor kDefaultFrameBorderInactiveColorLight = {166, 166, 166}; // #A6A6A6
[[maybe_unused]] static constexpr const QColor kDefaultSystemButtonHoverColor = {204, 204, 204}; // #CCCCCC
[[maybe_unused]] static constexpr const QColor kDefaultSystemButtonPressColor = {179, 179, 179}; // #B3B3B3
[[maybe_unused]] static constexpr const QColor kDefaultSystemCloseButtonHoverColor = {232, 17, 35}; // #E81123
[[maybe_unused]] static constexpr const QColor kDefaultSystemCloseButtonPressColor = {241, 112, 122}; // #F1707A
[[maybe_unused]] static constexpr const QColor kDefaultSystemButtonBackgroundColor = {204, 204, 204}; // #CCCCCC
[[maybe_unused]] static constexpr const QColor kDefaultSystemCloseButtonBackgroundColor = {232, 17, 35}; // #E81123
[[maybe_unused]] static constexpr const QSize kDefaultSystemButtonSize = {int(qRound(qreal(kDefaultTitleBarHeight) * 1.5)), kDefaultTitleBarHeight};
[[maybe_unused]] static constexpr const QSize kDefaultSystemButtonIconSize = {16, 16};
@ -191,7 +193,7 @@ enum class Option : int
DontDrawTopWindowFrameBorder = 0x00000004, // Windows only, don't draw the top window frame border even if the window frame border is visible.
EnableRoundedWindowCorners = 0x00000008, // Not implemented yet.
TransparentWindowBackground = 0x00000010, // Not implemented yet.
MaximizeButtonDocking = 0x00000020, // Windows only, enable the window docking feature introduced in Windows 11.
MaximizeButtonDocking = 0x00000020, // Not implemented yet.
CreateStandardWindowLayout = 0x00000040, // Using this option will cause FramelessHelper create a homemade titlebar and a window layout to contain it. If your window has a layout already, the newly created layout will mess up your own layout.
BeCompatibleWithQtFramelessWindowHint = 0x00000080, // Windows only, make the code compatible with Qt::FramelessWindowHint. Don't use this option unless you really need that flag.
DontTouchQtInternals = 0x00000100, // Windows only, don't modify Qt's internal data.
@ -207,7 +209,8 @@ enum class Option : int
DontTouchHighDpiScalingPolicy = 0x00040000, // Don't change Qt's default high DPI scaling policy. Qt5 default: disabled, Qt6 default: enabled.
DontTouchScaleFactorRoundingPolicy = 0x00080000, // Don't change Qt's default scale factor rounding policy. Qt5 default: round, Qt6 default: pass through.
DontTouchProcessDpiAwarenessLevel = 0x00100000, // Windows only, don't change the current process's DPI awareness level.
DontEnsureNonNativeWidgetSiblings = 0x00200000 // Don't ensure that siblings of native widgets stay non-native.
DontEnsureNonNativeWidgetSiblings = 0x00200000, // Don't ensure that siblings of native widgets stay non-native.
SyncNativeControlsThemeWithSystem = 0x00400000 // Windows only, sync the native Win32 controls' theme with system theme.
};
Q_ENUM_NS(Option)
Q_DECLARE_FLAGS(Options, Option)
@ -264,6 +267,15 @@ enum class Anchor : int
};
Q_ENUM_NS(Anchor)
enum class ButtonState : int
{
Unspecified = -1,
Hovered = 0,
Pressed = 1,
Released = 2
};
Q_ENUM_NS(ButtonState)
using GetWindowFlagsCallback = std::function<Qt::WindowFlags()>;
using SetWindowFlagsCallback = std::function<void(const Qt::WindowFlags)>;
@ -298,6 +310,8 @@ struct UserSettings
Qt::WindowState startupState = Qt::WindowNoState;
Options options = {};
QPoint systemMenuOffset = {};
QPointer<QObject> windowIconButton = nullptr;
QPointer<QObject> contextHelpButton = nullptr;
QPointer<QObject> minimizeButton = nullptr;
QPointer<QObject> maximizeButton = nullptr;
QPointer<QObject> closeButton = nullptr;

View File

@ -52,12 +52,16 @@ FRAMELESSHELPER_CORE_API void moveWindowToDesktopCenter(
[[nodiscard]] FRAMELESSHELPER_CORE_API Global::SystemTheme getSystemTheme();
[[nodiscard]] FRAMELESSHELPER_CORE_API Qt::WindowState windowStatesToWindowState(
const Qt::WindowStates states);
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isThemeChangeEvent(const QEvent * const event);
[[nodiscard]] FRAMELESSHELPER_CORE_API QColor calculateSystemButtonBackgroundColor(
const Global::SystemButtonType button, const Global::ButtonState state);
#ifdef Q_OS_WINDOWS
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8OrGreater();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin8Point1OrGreater();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin10OrGreater();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin101607OrGreater();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin101809OrGreater();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isWin11OrGreater();
[[nodiscard]] FRAMELESSHELPER_CORE_API bool isDwmCompositionEnabled();
FRAMELESSHELPER_CORE_API void triggerFrameChange(const WId windowId);
@ -106,6 +110,7 @@ FRAMELESSHELPER_CORE_API void tryToBeCompatibleWithQtFramelessWindowHint(
const bool enable);
FRAMELESSHELPER_CORE_API void setAeroSnappingEnabled(const WId windowId, const bool enable);
FRAMELESSHELPER_CORE_API void tryToEnableHighestDpiAwarenessLevel();
FRAMELESSHELPER_CORE_API void updateGlobalWin32ControlsTheme(const WId windowId, const bool dark);
#endif // Q_OS_WINDOWS
} // namespace Utils

View File

@ -54,10 +54,8 @@ class FRAMELESSHELPER_QUICK_API FramelessQuickUtils : public QObject
Q_PROPERTY(QColor defaultSystemDarkColor READ defaultSystemDarkColor CONSTANT FINAL)
Q_PROPERTY(QSizeF defaultSystemButtonSize READ defaultSystemButtonSize CONSTANT FINAL)
Q_PROPERTY(QSizeF defaultSystemButtonIconSize READ defaultSystemButtonIconSize CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemButtonHoverColor READ defaultSystemButtonHoverColor CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemButtonPressColor READ defaultSystemButtonPressColor CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemCloseButtonHoverColor READ defaultSystemCloseButtonHoverColor CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemCloseButtonPressColor READ defaultSystemCloseButtonPressColor CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemButtonBackgroundColor READ defaultSystemButtonBackgroundColor CONSTANT FINAL)
Q_PROPERTY(QColor defaultSystemCloseButtonBackgroundColor READ defaultSystemCloseButtonBackgroundColor CONSTANT FINAL)
public:
explicit FramelessQuickUtils(QObject *parent = nullptr);
@ -73,10 +71,11 @@ public:
Q_NODISCARD static QColor defaultSystemDarkColor();
Q_NODISCARD static QSizeF defaultSystemButtonSize();
Q_NODISCARD static QSizeF defaultSystemButtonIconSize();
Q_NODISCARD static QColor defaultSystemButtonHoverColor();
Q_NODISCARD static QColor defaultSystemButtonPressColor();
Q_NODISCARD static QColor defaultSystemCloseButtonHoverColor();
Q_NODISCARD static QColor defaultSystemCloseButtonPressColor();
Q_NODISCARD static QColor defaultSystemButtonBackgroundColor();
Q_NODISCARD static QColor defaultSystemCloseButtonBackgroundColor();
Q_NODISCARD Q_INVOKABLE static QColor getSystemButtonBackgroundColor(
const Global::SystemButtonType button, const Global::ButtonState state);
Q_SIGNALS:
void darkModeEnabledChanged();

View File

@ -0,0 +1 @@
#include <standardsystembutton.h>

View File

@ -0,0 +1,25 @@
/*
* MIT License
*
* Copyright (C) 2022 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once

View File

@ -26,6 +26,7 @@
#include <QtCore/qmutex.h>
#include <QtGui/qevent.h>
#include <QtGui/qwindow.h>
#include "framelesswindowsmanager.h"
#include "utils.h"
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -81,6 +82,12 @@ bool FramelessHelperQt::eventFilter(QObject *object, QEvent *event)
if (!object || !event) {
return false;
}
// First detect whether we got a theme change event or not, if so,
// inform the user the system theme has changed.
if (Utils::isThemeChangeEvent(event)) {
Q_EMIT FramelessWindowsManager::instance()->systemThemeChanged();
return false;
}
// Only monitor window events.
if (!object->isWindowType()) {
return false;

View File

@ -41,6 +41,7 @@ struct Win32HelperData
{
UserSettings settings = {};
SystemParameters params = {};
UINT lastMessage = 0;
};
struct Win32Helper
@ -53,7 +54,7 @@ struct Win32Helper
Q_GLOBAL_STATIC(Win32Helper, g_win32Helper)
FRAMELESSHELPER_BYTEARRAY_CONSTANT2(Win32MessageTypeName, "windows_generic_MSG")
static const QString qThemeSettingChangeEventName = QUtf8String(kThemeSettingChangeEventName);
static const QString qThemeSettingChangeEventName = QString::fromWCharArray(kThemeSettingChangeEventName);
FRAMELESSHELPER_STRING_CONSTANT(MonitorFromWindow)
FRAMELESSHELPER_STRING_CONSTANT(GetMonitorInfoW)
FRAMELESSHELPER_STRING_CONSTANT(ScreenToClient)
@ -96,13 +97,20 @@ void FramelessHelperWin::addWindow(const UserSettings &settings, const SystemPar
}
Utils::updateInternalWindowFrameMargins(params.getWindowHandle(), true);
Utils::updateWindowFrameMargins(params.windowId, false);
if (!(settings.options & Option::DontTouchWindowFrameBorderColor)) {
if (Utils::isWin101607OrGreater()) {
const bool dark = Utils::shouldAppsUseDarkMode();
Utils::updateWindowFrameBorderColor(params.windowId, dark);
if (!(settings.options & Option::DontTouchWindowFrameBorderColor)) {
Utils::updateWindowFrameBorderColor(params.windowId, dark);
}
if (Utils::isWin101809OrGreater()) {
if (settings.options & Option::SyncNativeControlsThemeWithSystem) {
Utils::updateGlobalWin32ControlsTheme(params.windowId, dark);
}
}
}
}
bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *message, NATIVE_EVENT_RESULT_TYPE *result)
bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *message, QT_NATIVE_EVENT_RESULT_TYPE *result)
{
if ((eventType != kWin32MessageTypeName) || !message || !result) {
return false;
@ -126,6 +134,8 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
return false;
}
const Win32HelperData data = g_win32Helper()->data.value(windowId);
const UINT uMsg = msg->message;
g_win32Helper()->data[windowId].lastMessage = uMsg;
g_win32Helper()->mutex.unlock();
const bool frameBorderVisible = [&data]() -> bool {
if (data.settings.options & Option::ForceShowWindowFrameBorder) {
@ -136,9 +146,133 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
}
return Utils::isWindowFrameBorderVisible();
}();
const UINT uMsg = msg->message;
const WPARAM wParam = msg->wParam;
const LPARAM lParam = msg->lParam;
#if 0
const bool isNonClientMouseEvent = (((uMsg >= WM_NCMOUSEMOVE) && (uMsg <= WM_NCMBUTTONDBLCLK))
|| (uMsg == WM_NCHITTEST));
const bool isClientMouseEvent = (((uMsg >= WM_MOUSEFIRST) && (uMsg <= WM_MOUSELAST))
|| ((uMsg >= WM_XBUTTONDOWN) && (uMsg <= WM_XBUTTONDBLCLK)));
const bool isMouseEvent = (isNonClientMouseEvent || isClientMouseEvent);
if ((data.settings.options & Option::MaximizeButtonDocking) && isMouseEvent) {
POINT nativeScreenPos = {};
POINT nativeClientPos = {};
if (isNonClientMouseEvent) {
nativeScreenPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
nativeClientPos = nativeScreenPos;
ScreenToClient(hWnd, &nativeClientPos);
}
if (isClientMouseEvent) {
nativeClientPos = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
nativeScreenPos = nativeClientPos;
ClientToScreen(hWnd, &nativeScreenPos);
}
const LPARAM screenPosLParam = MAKELPARAM(nativeScreenPos.x, nativeScreenPos.y);
const LPARAM clientPosLParam = MAKELPARAM(nativeClientPos.x, nativeClientPos.y);
const qreal devicePixelRatio = data.params.getWindowDevicePixelRatio();
const QPoint qtScenePos = QPointF(QPointF(qreal(nativeClientPos.x),
qreal(nativeClientPos.y)) / devicePixelRatio).toPoint();
SystemButtonType systemButton = SystemButtonType::Unknown;
if (data.params.isInsideSystemButtons(qtScenePos, &systemButton)) {
const int hitTestResult = [systemButton]() -> int {
switch (systemButton) {
case SystemButtonType::WindowIcon:
return HTSYSMENU;
case SystemButtonType::Help:
return HTHELP;
case SystemButtonType::Minimize:
return HTREDUCE;
case SystemButtonType::Maximize:
case SystemButtonType::Restore:
return HTZOOM;
case SystemButtonType::Close:
return HTCLOSE;
case SystemButtonType::Unknown:
return HTCAPTION;
}
return 0;
}();
Q_ASSERT(hitTestResult);
if ((uMsg == WM_NCHITTEST) && (hitTestResult != 0)) {
*result = hitTestResult;
return true;
}
if ((uMsg == WM_MOUSEMOVE) || (uMsg == WM_NCMOUSEMOVE)) {
bool translated = false;
switch (data.lastMessage) {
case WM_NCLBUTTONDOWN: {
PostMessageW(hWnd, WM_NCLBUTTONUP, hitTestResult, screenPosLParam);
translated = true;
} break;
case WM_NCMBUTTONDOWN: {
PostMessageW(hWnd, WM_NCMBUTTONUP, hitTestResult, screenPosLParam);
translated = true;
} break;
case WM_NCRBUTTONDOWN: {
PostMessageW(hWnd, WM_NCRBUTTONUP, hitTestResult, screenPosLParam);
translated = true;
} break;
default:
break;
}
if (translated) {
*result = 0;
return true;
}
if (uMsg == WM_NCMOUSEMOVE) {
PostMessageW(hWnd, WM_MOUSEMOVE, 0, clientPosLParam);
*result = 0;
return true;
}
}
bool translated = false;
switch (uMsg) {
case WM_NCLBUTTONDOWN: {
PostMessageW(hWnd, WM_LBUTTONDOWN, 0, clientPosLParam);
translated = true;
} break;
case WM_NCLBUTTONUP: {
PostMessageW(hWnd, WM_LBUTTONUP, 0, clientPosLParam);
translated = true;
} break;
case WM_NCLBUTTONDBLCLK: {
PostMessageW(hWnd, WM_LBUTTONDBLCLK, 0, clientPosLParam);
translated = true;
} break;
case WM_NCMBUTTONDOWN: {
PostMessageW(hWnd, WM_MBUTTONDOWN, 0, clientPosLParam);
translated = true;
} break;
case WM_NCMBUTTONUP: {
PostMessageW(hWnd, WM_MBUTTONUP, 0, clientPosLParam);
translated = true;
} break;
case WM_NCMBUTTONDBLCLK: {
PostMessageW(hWnd, WM_MBUTTONDBLCLK, 0, clientPosLParam);
translated = true;
} break;
case WM_NCRBUTTONDOWN: {
PostMessageW(hWnd, WM_RBUTTONDOWN, 0, clientPosLParam);
translated = true;
} break;
case WM_NCRBUTTONUP: {
PostMessageW(hWnd, WM_RBUTTONUP, 0, clientPosLParam);
translated = true;
} break;
case WM_NCRBUTTONDBLCLK: {
PostMessageW(hWnd, WM_RBUTTONDBLCLK, 0, clientPosLParam);
translated = true;
} break;
default:
break;
}
if (translated) {
*result = 0;
return true;
}
}
}
#endif
switch (uMsg) {
case WM_NCCALCSIZE: {
// Windows是根据这个消息的返回值来设置窗口的客户区窗口中真正显示的内容
@ -362,21 +496,6 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
}
}
}
#if 0
// Fix the flickering issue while resizing.
// "clientRect->right += 1;" also works.
// This small technique is known to have two draw backs:
// (1) Qt's coordinate system will be confused because the canvas size
// doesn't match the client area size so you will get some warnings
// from Qt and you should also be careful when you try to draw something
// manually through QPainter or in Qt Quick, be aware of the coordinate
// mismatch issue when you calculate position yourself.
// (2) Qt's window system will take some wrong actions when the window
// is being resized. For example, the window size will become 1px smaller
// or bigger everytime when resize() is called because the client area size
// is not correct. It confuses QPA's internal logic.
clientRect->bottom += 1;
#endif
Utils::syncWmPaintWithDwm(); // This should be executed at the very last.
// By returning WVR_REDRAW we can make the window resizing look less broken.
// But we must return 0 if wParam is FALSE, according to Microsoft Docs.
@ -466,32 +585,6 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
qWarning() << Utils::getSystemErrorMessage(kScreenToClient);
break;
}
if (data.settings.options & Option::MaximizeButtonDocking) {
const QPoint scenePos = QPointF(QPointF(qreal(localPos.x), qreal(localPos.y))
/ data.params.getWindowDevicePixelRatio()).toPoint();
SystemButtonType systemButton = SystemButtonType::Unknown;
if (data.params.isInsideSystemButtons(scenePos, &systemButton)) {
switch (systemButton) {
case SystemButtonType::Help:
*result = HTHELP;
break;
case SystemButtonType::Minimize:
*result = HTREDUCE;
break;
case SystemButtonType::Maximize:
case SystemButtonType::Restore:
*result = HTZOOM;
break;
case SystemButtonType::Close:
*result = HTCLOSE;
break;
default:
*result = HTCAPTION;
break;
}
return true;
}
}
const bool max = IsMaximized(hWnd);
const bool full = Utils::isFullScreen(windowId);
const int frameSizeY = Utils::getResizeBorderThickness(windowId, false, true);
@ -685,25 +778,26 @@ bool FramelessHelperWin::nativeEventFilter(const QByteArray &eventType, void *me
break;
}
}
const bool themeSettingChanged = [uMsg, wParam, lParam]() -> bool {
if (Utils::isWin101607OrGreater()) {
if (uMsg == WM_SETTINGCHANGE) {
if ((wParam == 0) && (QString::fromWCharArray(reinterpret_cast<LPCWSTR>(lParam))
.compare(qThemeSettingChangeEventName, Qt::CaseInsensitive) == 0)) {
return true;
bool systemThemeChanged = ((uMsg == WM_THEMECHANGED) || (uMsg == WM_SYSCOLORCHANGE)
|| (uMsg == WM_DWMCOLORIZATIONCOLORCHANGED));
if (Utils::isWin101607OrGreater()) {
if (uMsg == WM_SETTINGCHANGE) {
if ((wParam == 0) && (QString::fromWCharArray(reinterpret_cast<LPCWSTR>(lParam))
.compare(qThemeSettingChangeEventName, Qt::CaseInsensitive) == 0)) {
systemThemeChanged = true;
const bool dark = Utils::shouldAppsUseDarkMode();
if (!(data.settings.options & Option::DontTouchWindowFrameBorderColor)) {
Utils::updateWindowFrameBorderColor(windowId, dark);
}
if (Utils::isWin101809OrGreater()) {
if (data.settings.options & Option::SyncNativeControlsThemeWithSystem) {
Utils::updateGlobalWin32ControlsTheme(windowId, dark);
}
}
}
}
return false;
}();
if (themeSettingChanged) {
if (!(data.settings.options & Option::DontTouchWindowFrameBorderColor)) {
const bool dark = Utils::shouldAppsUseDarkMode();
Utils::updateWindowFrameBorderColor(windowId, dark);
}
}
if (themeSettingChanged || (uMsg == WM_THEMECHANGED)
|| (uMsg == WM_SYSCOLORCHANGE) || (uMsg == WM_DWMCOLORIZATIONCOLORCHANGED)) {
if (systemThemeChanged) {
Q_EMIT FramelessWindowsManager::instance()->systemThemeChanged();
}
return false;

View File

@ -182,14 +182,17 @@ void FramelessHelper::Core::initialize(const Options options)
qRegisterMetaType<ResourceType>();
qRegisterMetaType<DwmColorizationArea>();
qRegisterMetaType<Anchor>();
qRegisterMetaType<ButtonState>();
qRegisterMetaType<UserSettings>();
qRegisterMetaType<SystemParameters>();
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
// Only needed by Qt5 Quick applications, it's hard to say whether it's a
// bug or a lack of features. The QML engine is having a hard time to find
// the correct type if the type has a long namespace with a deep hierarchy.
qRegisterMetaType<SystemButtonType>("Global::SystemButtonType");
qRegisterMetaType<Anchor>("Global::Anchor");
qRegisterMetaType<ButtonState>("Global::ButtonState");
#endif
qRegisterMetaType<UserSettings>();
qRegisterMetaType<SystemParameters>();
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -218,4 +218,33 @@ Qt::WindowState Utils::windowStatesToWindowState(const Qt::WindowStates states)
return Qt::WindowNoState;
}
bool Utils::isThemeChangeEvent(const QEvent * const event)
{
Q_ASSERT(event);
if (!event) {
return false;
}
const QEvent::Type type = event->type();
return ((type == QEvent::ThemeChange) || (type == QEvent::ApplicationPaletteChange));
}
QColor Utils::calculateSystemButtonBackgroundColor(const SystemButtonType button, const ButtonState state)
{
if ((state == ButtonState::Unspecified) || (state == ButtonState::Released)) {
return kDefaultTransparentColor;
}
const QColor result = [button]() -> QColor {
if (button == SystemButtonType::Close) {
return kDefaultSystemCloseButtonBackgroundColor;
}
#ifdef Q_OS_WINDOWS
if (isTitleBarColorized()) {
return getDwmColorizationColor();
}
#endif
return kDefaultSystemButtonBackgroundColor;
}();
return ((state == ButtonState::Hovered) ? result.lighter(110) : result.lighter(105));
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -68,9 +68,9 @@ struct Win32UtilsHelper
Q_GLOBAL_STATIC(Win32UtilsHelper, g_utilsHelper)
static const QString qDwmRegistryKey = QUtf8String(kDwmRegistryKey);
static const QString qPersonalizeRegistryKey = QUtf8String(kPersonalizeRegistryKey);
static const QString qDwmColorKeyName = QUtf8String(kDwmColorKeyName);
static const QString qDwmRegistryKey = QString::fromWCharArray(kDwmRegistryKey);
static const QString qPersonalizeRegistryKey = QString::fromWCharArray(kPersonalizeRegistryKey);
static const QString qDwmColorKeyName = QString::fromWCharArray(kDwmColorKeyName);
FRAMELESSHELPER_STRING_CONSTANT2(SuccessMessageText, "The operation completed successfully.")
FRAMELESSHELPER_STRING_CONSTANT2(FormatMessageEmptyResult, "\"FormatMessageW()\" returned empty string.")
FRAMELESSHELPER_STRING_CONSTANT2(ErrorMessageTemplate, "Function \"%1()\" failed with error code %2: %3.")
@ -83,6 +83,7 @@ FRAMELESSHELPER_STRING_CONSTANT(dwmapi)
FRAMELESSHELPER_STRING_CONSTANT(winmm)
FRAMELESSHELPER_STRING_CONSTANT(shcore)
FRAMELESSHELPER_STRING_CONSTANT(d2d1)
FRAMELESSHELPER_STRING_CONSTANT(uxtheme)
FRAMELESSHELPER_STRING_CONSTANT(GetWindowRect)
FRAMELESSHELPER_STRING_CONSTANT(DwmIsCompositionEnabled)
FRAMELESSHELPER_STRING_CONSTANT(SetWindowPos)
@ -727,6 +728,18 @@ bool Utils::isWin101607OrGreater()
return result;
}
bool Utils::isWin101809OrGreater()
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10_1809);
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
static const bool result = (QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 17763));
#else
static const bool result = isWindowsVersionOrGreater(10, 0, 17763);
#endif
return result;
}
bool Utils::isHighContrastModeEnabled()
{
HIGHCONTRASTW hc;
@ -935,6 +948,7 @@ void Utils::fixupQtInternals(const WId windowId)
return;
}
const auto hwnd = reinterpret_cast<HWND>(windowId);
#if 0
SetLastError(ERROR_SUCCESS);
const auto oldClassStyle = static_cast<DWORD>(GetClassLongPtrW(hwnd, GCL_STYLE));
if (oldClassStyle == 0) {
@ -947,12 +961,22 @@ void Utils::fixupQtInternals(const WId windowId)
qWarning() << getSystemErrorMessage(kSetClassLongPtrW);
return;
}
#endif
SetLastError(ERROR_SUCCESS);
const auto oldWindowStyle = static_cast<DWORD>(GetWindowLongPtrW(hwnd, GWL_STYLE));
if (oldWindowStyle == 0) {
qWarning() << getSystemErrorMessage(kGetWindowLongPtrW);
return;
}
// Qt by default adds the "WS_POPUP" flag to all Win32 windows it created and maintained,
// which is not a good thing (although it won't cause any obvious issues in most cases
// either), because popup windows have some different behavior with normal overlapped
// windows, for example, it will affect DWM's default policy. And Qt will also not add
// the "WS_OVERLAPPED" flag to the windows in some cases, which also causes some trouble
// for us. To avoid some weird bugs, we do the correction here: remove the WS_POPUP flag
// and add the WS_OVERLAPPED flag, unconditionally. If your window really don't need this
// correction, it also means you should not use this framework, because without this
// correction, our core frameless functionality will be broken in some degree.
const DWORD newWindowStyle = ((oldWindowStyle & ~WS_POPUP) | WS_OVERLAPPED);
SetLastError(ERROR_SUCCESS);
if (SetWindowLongPtrW(hwnd, GWL_STYLE, static_cast<LONG_PTR>(newWindowStyle)) == 0) {
@ -1195,6 +1219,8 @@ void Utils::tryToEnableHighestDpiAwarenessLevel()
reinterpret_cast<decltype(&SetProcessDpiAwareness)>(
QSystemLibrary::resolve(kshcore, "SetProcessDpiAwareness"));
if (pSetProcessDpiAwareness) {
// This enum value is our own extension, so don't check for "E_ACCESSDENIED"
// because it won't appear in anywhere outside of our own code.
if (SUCCEEDED(pSetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE_V2))) {
return;
}
@ -1224,4 +1250,25 @@ SystemTheme Utils::getSystemTheme()
return SystemTheme::Light;
}
void Utils::updateGlobalWin32ControlsTheme(const WId windowId, const bool dark)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
// There's no global dark theme for common Win32 controls before Win10 1809.
if (!isWin101809OrGreater()) {
return;
}
static const auto pSetWindowTheme =
reinterpret_cast<decltype(&SetWindowTheme)>(
QSystemLibrary::resolve(kuxtheme, "SetWindowTheme"));
if (!pSetWindowTheme) {
return;
}
const auto hwnd = reinterpret_cast<HWND>(windowId);
// The result depends on the runtime system version, no need to check.
pSetWindowTheme(hwnd, (dark ? kSystemDarkThemeResourceName : kSystemLightThemeResourceName), nullptr);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -1,8 +1,8 @@
<RCC>
<qresource prefix="/org.wangwenx190.FramelessHelper">
<file>qml/CloseButton.qml</file>
<file>qml/MaximizeButton.qml</file>
<file>qml/MinimizeButton.qml</file>
<file>qml/StandardCloseButton.qml</file>
<file>qml/StandardMaximizeButton.qml</file>
<file>qml/StandardMinimizeButton.qml</file>
<file>qml/StandardTitleBar.qml</file>
</qresource>
</RCC>

View File

@ -93,9 +93,9 @@ void FramelessHelper::Quick::registerTypes(QQmlEngine *engine)
qmlRegisterAnonymousType2(QQuickWindow, QUICK_URI_SHORT);
qmlRegisterType<FramelessQuickWindow>(QUICK_URI_EXPAND("FramelessWindow"));
initResource();
qmlRegisterFile("MinimizeButton");
qmlRegisterFile("MaximizeButton");
qmlRegisterFile("CloseButton");
qmlRegisterFile("StandardMinimizeButton");
qmlRegisterFile("StandardMaximizeButton");
qmlRegisterFile("StandardCloseButton");
qmlRegisterFile("StandardTitleBar");
}

View File

@ -118,24 +118,19 @@ QSizeF FramelessQuickUtils::defaultSystemButtonIconSize()
return kDefaultSystemButtonIconSize;
}
QColor FramelessQuickUtils::defaultSystemButtonHoverColor()
QColor FramelessQuickUtils::defaultSystemButtonBackgroundColor()
{
return kDefaultSystemButtonHoverColor;
return kDefaultSystemButtonBackgroundColor;
}
QColor FramelessQuickUtils::defaultSystemButtonPressColor()
QColor FramelessQuickUtils::defaultSystemCloseButtonBackgroundColor()
{
return kDefaultSystemButtonPressColor;
return kDefaultSystemCloseButtonBackgroundColor;
}
QColor FramelessQuickUtils::defaultSystemCloseButtonHoverColor()
QColor FramelessQuickUtils::getSystemButtonBackgroundColor(const SystemButtonType button, const ButtonState state)
{
return kDefaultSystemCloseButtonHoverColor;
}
QColor FramelessQuickUtils::defaultSystemCloseButtonPressColor()
{
return kDefaultSystemCloseButtonPressColor;
return Utils::calculateSystemButtonBackgroundColor(button, state);
}
FRAMELESSHELPER_END_NAMESPACE

View File

@ -28,6 +28,7 @@ import org.wangwenx190.FramelessHelper 1.0
Button {
id: button
objectName: "CloseButtonObject"
implicitWidth: FramelessUtils.defaultSystemButtonSize.width
implicitHeight: FramelessUtils.defaultSystemButtonSize.height
contentItem: Item {
@ -42,15 +43,7 @@ Button {
}
background: Rectangle {
visible: button.hovered || button.pressed
color: {
if (button.pressed) {
return FramelessUtils.defaultSystemCloseButtonPressColor;
}
if (button.hovered) {
return FramelessUtils.defaultSystemCloseButtonHoverColor;
}
return "transparent";
}
color: FramelessUtils.getSystemButtonBackgroundColor(FramelessHelper.Close, (button.pressed ? FramelessHelper.Pressed : FramelessHelper.Hovered))
}
ToolTip {

View File

@ -30,6 +30,7 @@ Button {
property bool maximized: false
id: button
objectName: "MaximizeButtonObject"
implicitWidth: FramelessUtils.defaultSystemButtonSize.width
implicitHeight: FramelessUtils.defaultSystemButtonSize.height
contentItem: Item {
@ -47,15 +48,7 @@ Button {
}
background: Rectangle {
visible: button.hovered || button.pressed
color: {
if (button.pressed) {
return FramelessUtils.defaultSystemButtonPressColor;
}
if (button.hovered) {
return FramelessUtils.defaultSystemButtonHoverColor;
}
return "transparent";
}
color: FramelessUtils.getSystemButtonBackgroundColor(FramelessHelper.Maximize, (button.pressed ? FramelessHelper.Pressed : FramelessHelper.Hovered))
}
ToolTip {

View File

@ -28,6 +28,7 @@ import org.wangwenx190.FramelessHelper 1.0
Button {
id: button
objectName: "MinimizeButtonObject"
implicitWidth: FramelessUtils.defaultSystemButtonSize.width
implicitHeight: FramelessUtils.defaultSystemButtonSize.height
contentItem: Item {
@ -42,15 +43,7 @@ Button {
}
background: Rectangle {
visible: button.hovered || button.pressed
color: {
if (button.pressed) {
return FramelessUtils.defaultSystemButtonPressColor;
}
if (button.hovered) {
return FramelessUtils.defaultSystemButtonHoverColor;
}
return "transparent";
}
color: FramelessUtils.getSystemButtonBackgroundColor(FramelessHelper.Minimize, (button.pressed ? FramelessHelper.Pressed : FramelessHelper.Hovered))
}
ToolTip {

View File

@ -58,16 +58,16 @@ Rectangle {
right: parent.right
}
MinimizeButton {
StandardMinimizeButton {
id: minimizeButton
}
MaximizeButton {
StandardMaximizeButton {
id: maximizeButton
maximized: titleBar.maximized
}
CloseButton {
StandardCloseButton {
id: closeButton
}
}

View File

@ -31,9 +31,11 @@ set(SOURCES
${INCLUDE_PREFIX}/framelesswidgetshelper.h
${INCLUDE_PREFIX}/framelesswidget.h
${INCLUDE_PREFIX}/framelessmainwindow.h
${INCLUDE_PREFIX}/standardsystembutton.h
framelessmainwindow.cpp
framelesswidgetshelper.cpp
framelesswidget.cpp
standardsystembutton.cpp
)
if(WIN32 AND NOT FRAMELESSHELPER_BUILD_STATIC)

View File

@ -515,27 +515,40 @@ bool FramelessWidgetsHelper::isInSystemButtons(const QPoint &pos, SystemButtonTy
return false;
}
*button = SystemButtonType::Unknown;
if (!m_settings.minimizeButton || !m_settings.maximizeButton || !m_settings.closeButton) {
return false;
if (m_settings.windowIconButton && m_settings.windowIconButton->isWidgetType()) {
const auto iconBtn = qobject_cast<QWidget *>(m_settings.windowIconButton);
if (iconBtn->geometry().contains(pos)) {
*button = SystemButtonType::WindowIcon;
return true;
}
}
if (!m_settings.minimizeButton->isWidgetType() || !m_settings.maximizeButton->isWidgetType()
|| !m_settings.closeButton->isWidgetType()) {
return false;
if (m_settings.contextHelpButton && m_settings.contextHelpButton->isWidgetType()) {
const auto helpBtn = qobject_cast<QWidget *>(m_settings.contextHelpButton);
if (helpBtn->geometry().contains(pos)) {
*button = SystemButtonType::Help;
return true;
}
}
const auto minBtn = qobject_cast<QWidget *>(m_settings.minimizeButton);
if (minBtn->geometry().contains(pos)) {
*button = SystemButtonType::Minimize;
return true;
if (m_settings.minimizeButton && m_settings.minimizeButton->isWidgetType()) {
const auto minBtn = qobject_cast<QWidget *>(m_settings.minimizeButton);
if (minBtn->geometry().contains(pos)) {
*button = SystemButtonType::Minimize;
return true;
}
}
const auto maxBtn = qobject_cast<QWidget *>(m_settings.maximizeButton);
if (maxBtn->geometry().contains(pos)) {
*button = SystemButtonType::Maximize;
return true;
if (m_settings.maximizeButton && m_settings.maximizeButton->isWidgetType()) {
const auto maxBtn = qobject_cast<QWidget *>(m_settings.maximizeButton);
if (maxBtn->geometry().contains(pos)) {
*button = SystemButtonType::Maximize;
return true;
}
}
const auto closeBtn = qobject_cast<QWidget *>(m_settings.closeButton);
if (closeBtn->geometry().contains(pos)) {
*button = SystemButtonType::Close;
return true;
if (m_settings.closeButton && m_settings.closeButton->isWidgetType()) {
const auto closeBtn = qobject_cast<QWidget *>(m_settings.closeButton);
if (closeBtn->geometry().contains(pos)) {
*button = SystemButtonType::Close;
return true;
}
}
return false;
}

View File

@ -0,0 +1,25 @@
/*
* MIT License
*
* Copyright (C) 2022 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "standardsystembutton.h"

View File

@ -0,0 +1,25 @@
/*
* MIT License
*
* Copyright (C) 2022 by wangwenx190 (Yuhang Zhao)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "../../include/FramelessHelper/Widgets/standardsystembutton.h"