parent
db8c6b5cc5
commit
12a6f86850
|
@ -26,41 +26,56 @@
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
|
#include <QOperatingSystemVersion>
|
||||||
|
#include <QPainter>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
#ifdef QT_QUICK_LIB
|
#ifdef QT_QUICK_LIB
|
||||||
#include "framelessquickhelper.h"
|
#include "framelessquickhelper.h"
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
|
#include <QQmlProperty>
|
||||||
#endif
|
#endif
|
||||||
#include <QPainter>
|
|
||||||
#include <QVBoxLayout>
|
static inline bool shouldHaveWindowFrame()
|
||||||
#include <QWidget>
|
{
|
||||||
|
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10;
|
||||||
|
}
|
||||||
|
|
||||||
class FramelessWidget : public QWidget
|
class FramelessWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
// Q_DISABLE_COPY_MOVE(FramelessWidget) // Since Qt 5.13
|
Q_DISABLE_COPY_MOVE(FramelessWidget)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FramelessWidget(QWidget *parent = nullptr) : QWidget(parent) {}
|
explicit FramelessWidget(QWidget *parent = nullptr) : QWidget(parent)
|
||||||
|
{
|
||||||
|
isWin10OrGreater = shouldHaveWindowFrame();
|
||||||
|
}
|
||||||
~FramelessWidget() override = default;
|
~FramelessWidget() override = default;
|
||||||
|
|
||||||
|
bool isNormaled() const { return !isMinimized() && !isMaximized() && !isFullScreen(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *event) override
|
void paintEvent(QPaintEvent *event) override
|
||||||
{
|
{
|
||||||
QWidget::paintEvent(event);
|
QWidget::paintEvent(event);
|
||||||
QPainter painter(this);
|
if (isWin10OrGreater && isNormaled()) {
|
||||||
painter.save();
|
QPainter painter(this);
|
||||||
painter.setPen(isActiveWindow() ? borderColor_active : borderColor_inactive);
|
painter.save();
|
||||||
painter.drawLine(0, 0, width(), 0);
|
painter.setPen(isActiveWindow() ? borderColor_active : borderColor_inactive);
|
||||||
painter.drawLine(0, height(), width(), height());
|
painter.drawLine(0, 0, width(), 0);
|
||||||
painter.drawLine(0, 0, 0, height());
|
// painter.drawLine(0, height(), width(), height());
|
||||||
painter.drawLine(width(), 0, width(), height());
|
// painter.drawLine(0, 0, 0, height());
|
||||||
painter.restore();
|
// painter.drawLine(width(), 0, width(), height());
|
||||||
|
painter.restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const QColor borderColor_active = {"#707070"};
|
const QColor borderColor_active = {/*"#707070"*/ "#ffffff"};
|
||||||
const QColor borderColor_inactive = {"#aaaaaa"};
|
const QColor borderColor_inactive = {"#aaaaaa"};
|
||||||
|
bool isWin10OrGreater = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
@ -163,6 +178,11 @@ int main(int argc, char *argv[])
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
engine.load(mainQmlUrl);
|
engine.load(mainQmlUrl);
|
||||||
|
QList<QObject *> rootObjs = engine.rootObjects();
|
||||||
|
Q_ASSERT(!rootObjs.isEmpty());
|
||||||
|
QObject *rootObj = rootObjs.at(0);
|
||||||
|
Q_ASSERT(rootObj);
|
||||||
|
QQmlProperty::write(rootObj, QString::fromUtf8("isWin10OrGreater"), shouldHaveWindowFrame());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return QApplication::exec();
|
return QApplication::exec();
|
||||||
|
|
|
@ -9,13 +9,21 @@ Window {
|
||||||
height: 600
|
height: 600
|
||||||
title: qsTr("Hello, World!")
|
title: qsTr("Hello, World!")
|
||||||
|
|
||||||
|
property bool isWin10OrGreater: false
|
||||||
|
|
||||||
FramelessHelper {
|
FramelessHelper {
|
||||||
id: framelessHelper
|
id: framelessHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
visible: isWin10OrGreater && (window.visibility === Window.Windowed)
|
||||||
border.color: Qt.application.state === Qt.ApplicationActive ? "#707070" : "#aaaaaa"
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
height: 1
|
||||||
|
color: Qt.application.state === Qt.ApplicationActive ? /*"#707070"*/ "#ffffff" : "#aaaaaa"
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
|
@ -100,7 +100,7 @@ const UINT m_defaultDotsPerInch = USER_DEFAULT_SCREEN_DPI;
|
||||||
|
|
||||||
const qreal m_defaultDevicePixelRatio = 1.0;
|
const qreal m_defaultDevicePixelRatio = 1.0;
|
||||||
|
|
||||||
bool isWin8OrGreator()
|
bool isWin8OrGreater()
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||||
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8;
|
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8;
|
||||||
|
@ -109,7 +109,7 @@ bool isWin8OrGreator()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWin8Point1OrGreator()
|
bool isWin8Point1OrGreater()
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||||
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1;
|
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows8_1;
|
||||||
|
@ -118,7 +118,7 @@ bool isWin8Point1OrGreator()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isWin10OrGreator(const int ver)
|
bool isWin10OrGreater(const int ver)
|
||||||
{
|
{
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||||
return QOperatingSystemVersion::current()
|
return QOperatingSystemVersion::current()
|
||||||
|
@ -129,6 +129,15 @@ bool isWin10OrGreator(const int ver)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shouldHaveWindowFrame()
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0))
|
||||||
|
return QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows10;
|
||||||
|
#else
|
||||||
|
return QSysInfo::WindowsVersion >= QSysInfo::WV_WINDOWS10;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef WNEF_GENERATE_WINAPI
|
#ifndef WNEF_GENERATE_WINAPI
|
||||||
#define WNEF_GENERATE_WINAPI(funcName, resultType, ...) \
|
#define WNEF_GENERATE_WINAPI(funcName, resultType, ...) \
|
||||||
using _WNEF_WINAPI_##funcName = resultType(WINAPI *)(__VA_ARGS__); \
|
using _WNEF_WINAPI_##funcName = resultType(WINAPI *)(__VA_ARGS__); \
|
||||||
|
@ -411,19 +420,19 @@ void loadDPIFunctions()
|
||||||
}
|
}
|
||||||
resolved = true;
|
resolved = true;
|
||||||
// Available since Windows 8.1
|
// Available since Windows 8.1
|
||||||
if (isWin8Point1OrGreator()) {
|
if (isWin8Point1OrGreater()) {
|
||||||
WNEF_RESOLVE_WINAPI(SHCore, GetDpiForMonitor)
|
WNEF_RESOLVE_WINAPI(SHCore, GetDpiForMonitor)
|
||||||
WNEF_RESOLVE_WINAPI(SHCore, GetProcessDpiAwareness)
|
WNEF_RESOLVE_WINAPI(SHCore, GetProcessDpiAwareness)
|
||||||
}
|
}
|
||||||
// Available since Windows 10, version 1607 (10.0.14393)
|
// Available since Windows 10, version 1607 (10.0.14393)
|
||||||
if (isWin10OrGreator(14393)) {
|
if (isWin10OrGreater(14393)) {
|
||||||
WNEF_RESOLVE_WINAPI(User32, GetDpiForWindow)
|
WNEF_RESOLVE_WINAPI(User32, GetDpiForWindow)
|
||||||
WNEF_RESOLVE_WINAPI(User32, GetDpiForSystem)
|
WNEF_RESOLVE_WINAPI(User32, GetDpiForSystem)
|
||||||
WNEF_RESOLVE_WINAPI(User32, GetSystemMetricsForDpi)
|
WNEF_RESOLVE_WINAPI(User32, GetSystemMetricsForDpi)
|
||||||
WNEF_RESOLVE_WINAPI(User32, AdjustWindowRectExForDpi)
|
WNEF_RESOLVE_WINAPI(User32, AdjustWindowRectExForDpi)
|
||||||
}
|
}
|
||||||
// Available since Windows 10, version 1803 (10.0.17134)
|
// Available since Windows 10, version 1803 (10.0.17134)
|
||||||
if (isWin10OrGreator(17134)) {
|
if (isWin10OrGreater(17134)) {
|
||||||
WNEF_RESOLVE_WINAPI(User32, GetSystemDpiForProcess)
|
WNEF_RESOLVE_WINAPI(User32, GetSystemDpiForProcess)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -734,7 +743,7 @@ qreal GetDevicePixelRatioForWindow(const HWND handle)
|
||||||
return GetPreferedNumber(result);
|
return GetPreferedNumber(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
RECT GetFrameSizeForWindow(const HWND handle, const bool includingTitleBar = false)
|
RECT GetFrameSizeForWindow(const HWND handle, const BOOL includingTitleBar = FALSE)
|
||||||
{
|
{
|
||||||
RECT rect = {0, 0, 0, 0};
|
RECT rect = {0, 0, 0, 0};
|
||||||
if (handle && WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) {
|
if (handle && WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) {
|
||||||
|
@ -773,26 +782,38 @@ void UpdateFrameMarginsForWindow(const HWND handle)
|
||||||
{
|
{
|
||||||
if (handle && WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) {
|
if (handle && WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) {
|
||||||
MARGINS margins = {0, 0, 0, 0};
|
MARGINS margins = {0, 0, 0, 0};
|
||||||
if (IsDwmCompositionEnabled()) {
|
// The frame shadow is drawn on the non-client area and thus we have
|
||||||
// The frame shadow is drawn on the non-client area and thus we have
|
// to make sure the non-client area rendering is enabled first.
|
||||||
// to make sure the non-client area rendering is enabled first.
|
const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
|
||||||
const DWMNCRENDERINGPOLICY ncrp = DWMNCRP_ENABLED;
|
WNEF_EXECUTE_WINAPI(DwmSetWindowAttribute,
|
||||||
WNEF_EXECUTE_WINAPI(DwmSetWindowAttribute,
|
handle,
|
||||||
handle,
|
DWMWA_NCRENDERING_POLICY,
|
||||||
DWMWA_NCRENDERING_POLICY,
|
&ncrp,
|
||||||
&ncrp,
|
sizeof(ncrp))
|
||||||
sizeof(ncrp))
|
// Use negative values have the same effect, however, it will
|
||||||
// Use negative values have the same effect, however, it will
|
// cause the window become transparent when it's maximizing or
|
||||||
// cause the window become transparent when it's maximizing or
|
// restoring from maximized. Just like flashing. Fixing it by
|
||||||
// restoring from maximized. Just like flashing. Fixing it by
|
// passing positive values.
|
||||||
// passing positive values.
|
// The system won't draw the frame shadow if the window doesn't
|
||||||
// The system won't draw the frame shadow if the window doesn't
|
// have a frame, so we have to extend the frame a bit to let the
|
||||||
// have a frame, so we have to extend the frame a bit to let the
|
// system draw the shadow. We won't see any frame even we have
|
||||||
// system draw the shadow. We won't see any frame even we have
|
// extended it because we have turned the whole window area into
|
||||||
// extended it because we have turned the whole window area into
|
// the client area in WM_NCCALCSIZE so we won't see it due to
|
||||||
// the client area in WM_NCCALCSIZE so we won't see it due to
|
// it's covered by the client area (in other words, it's still
|
||||||
// it's covered by the client area (in other words, it's still
|
// there, we just can't see it).
|
||||||
// there, we just can't see it).
|
if (shouldHaveWindowFrame()) {
|
||||||
|
const auto GetTopBorderHeight = [](const HWND handle) -> int {
|
||||||
|
if (handle && WNEF_EXECUTE_WINAPI_RETURN(IsWindow, FALSE, handle)) {
|
||||||
|
if (IsMaximized(handle) || IsFullScreen(handle) || !IsDwmCompositionEnabled()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
if (GetTopBorderHeight(handle) != 0) {
|
||||||
|
margins.cyTopHeight = GetFrameSizeForWindow(handle, TRUE).top;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
margins.cyTopHeight = 1;
|
margins.cyTopHeight = 1;
|
||||||
}
|
}
|
||||||
WNEF_EXECUTE_WINAPI(DwmExtendFrameIntoClientArea, handle, &margins)
|
WNEF_EXECUTE_WINAPI(DwmExtendFrameIntoClientArea, handle, &margins)
|
||||||
|
@ -1157,6 +1178,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
// it. So things will become quite complicated if you want to
|
// it. So things will become quite complicated if you want to
|
||||||
// preserve the four window borders. So we just remove the whole
|
// preserve the four window borders. So we just remove the whole
|
||||||
// window frame, otherwise the code will become much more complex.
|
// window frame, otherwise the code will become much more complex.
|
||||||
|
|
||||||
const auto mode = static_cast<BOOL>(msg->wParam);
|
const auto mode = static_cast<BOOL>(msg->wParam);
|
||||||
// If the window bounds change, we're going to relayout and repaint
|
// If the window bounds change, we're going to relayout and repaint
|
||||||
// anyway. Returning WVR_REDRAW avoids an extra paint before that of
|
// anyway. Returning WVR_REDRAW avoids an extra paint before that of
|
||||||
|
@ -1167,6 +1189,25 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
const auto clientRect = mode ? &(
|
const auto clientRect = mode ? &(
|
||||||
reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam)->rgrc[0])
|
reinterpret_cast<LPNCCALCSIZE_PARAMS>(msg->lParam)->rgrc[0])
|
||||||
: reinterpret_cast<LPRECT>(msg->lParam);
|
: reinterpret_cast<LPRECT>(msg->lParam);
|
||||||
|
if (shouldHaveWindowFrame()) {
|
||||||
|
// Store the original top before the default window proc
|
||||||
|
// applies the default frame.
|
||||||
|
const LONG originalTop = clientRect->top;
|
||||||
|
// Apply the default frame
|
||||||
|
const LRESULT ret = WNEF_EXECUTE_WINAPI_RETURN(DefWindowProcW,
|
||||||
|
0,
|
||||||
|
msg->hwnd,
|
||||||
|
WM_NCCALCSIZE,
|
||||||
|
msg->wParam,
|
||||||
|
msg->lParam);
|
||||||
|
if (ret != 0) {
|
||||||
|
*result = ret;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Re-apply the original top from before the size of the
|
||||||
|
// default frame was applied.
|
||||||
|
clientRect->top = originalTop;
|
||||||
|
}
|
||||||
// We don't need this correction when we're fullscreen. We will
|
// We don't need this correction when we're fullscreen. We will
|
||||||
// have the WS_POPUP size, so we don't have to worry about
|
// have the WS_POPUP size, so we don't have to worry about
|
||||||
// borders, and the default frame will be fine.
|
// borders, and the default frame will be fine.
|
||||||
|
@ -1178,12 +1219,14 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
// The value of border width and border height should be
|
// The value of border width and border height should be
|
||||||
// identical in most cases, when the scale factor is 1.0, it
|
// identical in most cases, when the scale factor is 1.0, it
|
||||||
// should be eight pixels.
|
// should be eight pixels.
|
||||||
const int bw = getSystemMetric(msg->hwnd, SystemMetric::BorderWidth, true);
|
|
||||||
const int bh = getSystemMetric(msg->hwnd, SystemMetric::BorderHeight, true);
|
const int bh = getSystemMetric(msg->hwnd, SystemMetric::BorderHeight, true);
|
||||||
clientRect->top += bh;
|
clientRect->top += bh;
|
||||||
clientRect->bottom -= bh;
|
if (!shouldHaveWindowFrame()) {
|
||||||
clientRect->left += bw;
|
clientRect->bottom -= bh;
|
||||||
clientRect->right -= bw;
|
const int bw = getSystemMetric(msg->hwnd, SystemMetric::BorderWidth, true);
|
||||||
|
clientRect->left += bw;
|
||||||
|
clientRect->right -= bw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Attempt to detect if there's an autohide taskbar, and if
|
// Attempt to detect if there's an autohide taskbar, and if
|
||||||
// there is, reduce our size a bit on the side with the taskbar,
|
// there is, reduce our size a bit on the side with the taskbar,
|
||||||
|
@ -1205,7 +1248,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
// Due to ABM_GETAUTOHIDEBAREX only exists from Win8.1,
|
// Due to ABM_GETAUTOHIDEBAREX only exists from Win8.1,
|
||||||
// we have to use another way to judge this if we are
|
// we have to use another way to judge this if we are
|
||||||
// running on Windows 7 or Windows 8.
|
// running on Windows 7 or Windows 8.
|
||||||
if (isWin8Point1OrGreator()) {
|
if (isWin8Point1OrGreater()) {
|
||||||
const MONITORINFO monitorInfo = GetMonitorInfoForWindow(msg->hwnd);
|
const MONITORINFO monitorInfo = GetMonitorInfoForWindow(msg->hwnd);
|
||||||
// This helper can be used to determine if there's a
|
// This helper can be used to determine if there's a
|
||||||
// auto-hide taskbar on the given edge of the monitor
|
// auto-hide taskbar on the given edge of the monitor
|
||||||
|
@ -1286,50 +1329,62 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
// area.
|
// area.
|
||||||
*result = 0;
|
*result = 0;
|
||||||
}
|
}
|
||||||
|
if (shouldHaveWindowFrame()) {
|
||||||
|
*result = 0;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
// These undocumented messages are sent to draw themed window
|
||||||
|
// borders. Block them to prevent drawing borders over the client
|
||||||
|
// area.
|
||||||
case WM_NCUAHDRAWCAPTION:
|
case WM_NCUAHDRAWCAPTION:
|
||||||
case WM_NCUAHDRAWFRAME: {
|
case WM_NCUAHDRAWFRAME: {
|
||||||
// These undocumented messages are sent to draw themed window
|
if (shouldHaveWindowFrame()) {
|
||||||
// borders. Block them to prevent drawing borders over the client
|
break;
|
||||||
// area.
|
} else {
|
||||||
*result = 0;
|
*result = 0;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case WM_NCPAINT: {
|
case WM_NCPAINT: {
|
||||||
// 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失
|
// 边框阴影处于非客户区的范围,因此如果直接阻止非客户区的绘制,会导致边框阴影丢失
|
||||||
|
|
||||||
if (!IsDwmCompositionEnabled()) {
|
if (!IsDwmCompositionEnabled() && !shouldHaveWindowFrame()) {
|
||||||
// Only block WM_NCPAINT when DWM composition is disabled. If
|
// Only block WM_NCPAINT when DWM composition is disabled. If
|
||||||
// it's blocked when DWM composition is enabled, the frame
|
// it's blocked when DWM composition is enabled, the frame
|
||||||
// shadow won't be drawn.
|
// shadow won't be drawn.
|
||||||
*result = 0;
|
*result = 0;
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case WM_NCACTIVATE: {
|
case WM_NCACTIVATE: {
|
||||||
if (IsDwmCompositionEnabled()) {
|
if (shouldHaveWindowFrame()) {
|
||||||
// DefWindowProc won't repaint the window border if lParam
|
break;
|
||||||
// (normally a HRGN) is -1. See the following link's "lParam"
|
|
||||||
// section:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
|
|
||||||
// Don't use "*result = 0" otherwise the window won't respond to
|
|
||||||
// the window active state change.
|
|
||||||
*result = WNEF_EXECUTE_WINAPI_RETURN(DefWindowProcW,
|
|
||||||
0,
|
|
||||||
msg->hwnd,
|
|
||||||
msg->message,
|
|
||||||
msg->wParam,
|
|
||||||
-1);
|
|
||||||
} else {
|
} else {
|
||||||
if (static_cast<BOOL>(msg->wParam)) {
|
if (IsDwmCompositionEnabled()) {
|
||||||
*result = FALSE;
|
// DefWindowProc won't repaint the window border if lParam
|
||||||
|
// (normally a HRGN) is -1. See the following link's "lParam"
|
||||||
|
// section:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-ncactivate
|
||||||
|
// Don't use "*result = 0" otherwise the window won't respond
|
||||||
|
// to the window active state change.
|
||||||
|
*result = WNEF_EXECUTE_WINAPI_RETURN(DefWindowProcW,
|
||||||
|
0,
|
||||||
|
msg->hwnd,
|
||||||
|
msg->message,
|
||||||
|
msg->wParam,
|
||||||
|
-1);
|
||||||
} else {
|
} else {
|
||||||
*result = TRUE;
|
if (static_cast<BOOL>(msg->wParam)) {
|
||||||
|
*result = FALSE;
|
||||||
|
} else {
|
||||||
|
*result = TRUE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
case WM_NCHITTEST: {
|
case WM_NCHITTEST: {
|
||||||
// 原生Win32窗口只有顶边是在窗口内部resize的,其余三边都是在窗口
|
// 原生Win32窗口只有顶边是在窗口内部resize的,其余三边都是在窗口
|
||||||
|
@ -1396,175 +1451,205 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
// looks terrible on old systems. I'm testing this solution in
|
// looks terrible on old systems. I'm testing this solution in
|
||||||
// another branch, if you are interested in it, you can give it a
|
// another branch, if you are interested in it, you can give it a
|
||||||
// try.
|
// try.
|
||||||
|
|
||||||
if (data->windowData.mouseTransparent) {
|
if (data->windowData.mouseTransparent) {
|
||||||
// Mouse events will be passed to the parent window.
|
// Mouse events will be passed to the parent window.
|
||||||
*result = HTTRANSPARENT;
|
*result = HTTRANSPARENT;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const auto getHTResult =
|
const auto isInSpecificAreas =
|
||||||
[](const HWND _hWnd, const LPARAM _lParam, const WINDOWDATA &_data) -> LRESULT {
|
[](const int x, const int y, const QList<QRect> &areas, const qreal dpr) -> bool {
|
||||||
const auto isInSpecificAreas = [](const int x,
|
if (!areas.isEmpty()) {
|
||||||
const int y,
|
for (auto &&area : qAsConst(areas)) {
|
||||||
const QList<QRect> &areas,
|
if (!area.isValid()) {
|
||||||
const qreal dpr) -> bool {
|
continue;
|
||||||
if (!areas.isEmpty()) {
|
}
|
||||||
for (auto &&area : qAsConst(areas)) {
|
if (QRectF(area.x() * dpr,
|
||||||
if (!area.isValid()) {
|
area.y() * dpr,
|
||||||
continue;
|
area.width() * dpr,
|
||||||
}
|
area.height() * dpr)
|
||||||
if (QRectF(area.x() * dpr,
|
.contains(x, y)) {
|
||||||
area.y() * dpr,
|
return true;
|
||||||
area.width() * dpr,
|
}
|
||||||
area.height() * dpr)
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
#if defined(QT_WIDGETS_LIB) || defined(QT_QUICK_LIB)
|
||||||
|
const auto isInSpecificObjects = [](const int x,
|
||||||
|
const int y,
|
||||||
|
const QList<QPointer<QObject>> &objects,
|
||||||
|
const qreal dpr) -> bool {
|
||||||
|
if (!objects.isEmpty()) {
|
||||||
|
for (auto &&object : qAsConst(objects)) {
|
||||||
|
if (!object) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#ifdef QT_WIDGETS_LIB
|
||||||
|
const auto widget = qobject_cast<QWidget *>(object);
|
||||||
|
if (widget) {
|
||||||
|
const QPoint pos = widget->mapToGlobal({0, 0});
|
||||||
|
if (QRectF(pos.x() * dpr,
|
||||||
|
pos.y() * dpr,
|
||||||
|
widget->width() * dpr,
|
||||||
|
widget->height() * dpr)
|
||||||
.contains(x, y)) {
|
.contains(x, y)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
#if defined(QT_WIDGETS_LIB) || defined(QT_QUICK_LIB)
|
|
||||||
const auto isInSpecificObjects = [](const int x,
|
|
||||||
const int y,
|
|
||||||
const QList<QPointer<QObject>> &objects,
|
|
||||||
const qreal dpr) -> bool {
|
|
||||||
if (!objects.isEmpty()) {
|
|
||||||
for (auto &&object : qAsConst(objects)) {
|
|
||||||
if (!object) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#ifdef QT_WIDGETS_LIB
|
|
||||||
const auto widget = qobject_cast<QWidget *>(object);
|
|
||||||
if (widget) {
|
|
||||||
const QPoint pos = widget->mapToGlobal({0, 0});
|
|
||||||
if (QRectF(pos.x() * dpr,
|
|
||||||
pos.y() * dpr,
|
|
||||||
widget->width() * dpr,
|
|
||||||
widget->height() * dpr)
|
|
||||||
.contains(x, y)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef QT_QUICK_LIB
|
#ifdef QT_QUICK_LIB
|
||||||
const auto quickItem = qobject_cast<QQuickItem *>(object);
|
const auto quickItem = qobject_cast<QQuickItem *>(object);
|
||||||
if (quickItem) {
|
if (quickItem) {
|
||||||
const QPointF pos = quickItem->mapToGlobal({0, 0});
|
const QPointF pos = quickItem->mapToGlobal({0, 0});
|
||||||
if (QRectF(pos.x() * dpr,
|
if (QRectF(pos.x() * dpr,
|
||||||
pos.y() * dpr,
|
pos.y() * dpr,
|
||||||
quickItem->width() * dpr,
|
quickItem->width() * dpr,
|
||||||
quickItem->height() * dpr)
|
quickItem->height() * dpr)
|
||||||
.contains(x, y)) {
|
.contains(x, y)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
};
|
return false;
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
RECT clientRect = {0, 0, 0, 0};
|
// Don't use HIWORD(lParam) and LOWORD(lParam) to get cursor
|
||||||
WNEF_EXECUTE_WINAPI(GetClientRect, _hWnd, &clientRect)
|
// coordinates because their results are unsigned numbers,
|
||||||
const LONG ww = clientRect.right;
|
// however the cursor position may be negative due to in a
|
||||||
const LONG wh = clientRect.bottom;
|
// different monitor.
|
||||||
// Don't use HIWORD(lParam) and LOWORD(lParam) to get cursor
|
const POINT globalMouse{GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam)};
|
||||||
// coordinates because their results are unsigned numbers,
|
POINT localMouse = globalMouse;
|
||||||
// however the cursor position may be negative due to in a
|
WNEF_EXECUTE_WINAPI(ScreenToClient, msg->hwnd, &localMouse)
|
||||||
// different monitor.
|
const auto &_data = data->windowData;
|
||||||
const POINT globalMouse{GET_X_LPARAM(_lParam), GET_Y_LPARAM(_lParam)};
|
const qreal dpr = GetDevicePixelRatioForWindow(msg->hwnd);
|
||||||
POINT mouse = globalMouse;
|
const bool isInIgnoreAreas = isInSpecificAreas(localMouse.x,
|
||||||
WNEF_EXECUTE_WINAPI(ScreenToClient, _hWnd, &mouse)
|
localMouse.y,
|
||||||
// These values should be DPI-aware.
|
_data.ignoreAreas,
|
||||||
const LONG bw = getSystemMetric(_hWnd, SystemMetric::BorderWidth, true);
|
dpr);
|
||||||
const LONG bh = getSystemMetric(_hWnd, SystemMetric::BorderHeight, true);
|
const bool customDragAreas = !_data.draggableAreas.isEmpty();
|
||||||
const LONG tbh = getSystemMetric(_hWnd, SystemMetric::TitleBarHeight, true);
|
const bool isInDraggableAreas = customDragAreas
|
||||||
const qreal dpr = GetDevicePixelRatioForWindow(_hWnd);
|
? isInSpecificAreas(localMouse.x,
|
||||||
const bool isInIgnoreAreas = isInSpecificAreas(mouse.x,
|
localMouse.y,
|
||||||
mouse.y,
|
_data.draggableAreas,
|
||||||
_data.ignoreAreas,
|
dpr)
|
||||||
dpr);
|
: true;
|
||||||
const bool customDragAreas = !_data.draggableAreas.isEmpty();
|
|
||||||
const bool isInDraggableAreas = customDragAreas
|
|
||||||
? isInSpecificAreas(mouse.x,
|
|
||||||
mouse.y,
|
|
||||||
_data.draggableAreas,
|
|
||||||
dpr)
|
|
||||||
: true;
|
|
||||||
#if defined(QT_WIDGETS_LIB) || defined(QT_QUICK_LIB)
|
#if defined(QT_WIDGETS_LIB) || defined(QT_QUICK_LIB)
|
||||||
const bool isInIgnoreObjects = isInSpecificObjects(globalMouse.x,
|
const bool isInIgnoreObjects = isInSpecificObjects(globalMouse.x,
|
||||||
globalMouse.y,
|
globalMouse.y,
|
||||||
_data.ignoreObjects,
|
_data.ignoreObjects,
|
||||||
dpr);
|
dpr);
|
||||||
const bool customDragObjects = !_data.draggableObjects.isEmpty();
|
const bool customDragObjects = !_data.draggableObjects.isEmpty();
|
||||||
const bool isInDraggableObjects = customDragObjects
|
const bool isInDraggableObjects = customDragObjects
|
||||||
? isInSpecificObjects(globalMouse.x,
|
? isInSpecificObjects(globalMouse.x,
|
||||||
globalMouse.y,
|
globalMouse.y,
|
||||||
_data.draggableObjects,
|
_data.draggableObjects,
|
||||||
dpr)
|
dpr)
|
||||||
: true;
|
: true;
|
||||||
#else
|
#else
|
||||||
// Don't block resizing if both of the Qt Widgets module and Qt
|
// Don't block resizing if both of the Qt Widgets module and Qt
|
||||||
// Quick module are not compiled in, although there's not much
|
// Quick module are not compiled in, although there's not much
|
||||||
// significance of using this code in this case.
|
// significance of using this code in this case.
|
||||||
const bool isInIgnoreObjects = false;
|
const bool isInIgnoreObjects = false;
|
||||||
const bool isInDraggableObjects = true;
|
const bool isInDraggableObjects = true;
|
||||||
const bool customDragObjects = false;
|
const bool customDragObjects = false;
|
||||||
#endif
|
#endif
|
||||||
const bool customDrag = customDragAreas || customDragObjects;
|
const bool customDrag = customDragAreas || customDragObjects;
|
||||||
const bool isResizePermitted = !isInIgnoreAreas && !isInIgnoreObjects;
|
const bool isResizePermitted = !isInIgnoreAreas && !isInIgnoreObjects;
|
||||||
const bool isTitleBar = (customDrag ? (isInDraggableAreas && isInDraggableObjects)
|
const LONG bh = getSystemMetric(msg->hwnd, SystemMetric::BorderHeight, true);
|
||||||
: (mouse.y <= (tbh + bh)))
|
const LONG tbh = getSystemMetric(msg->hwnd, SystemMetric::TitleBarHeight, true);
|
||||||
&& isResizePermitted && !_data.disableTitleBar;
|
const bool isTitleBar = (customDrag ? (isInDraggableAreas && isInDraggableObjects)
|
||||||
if (IsMaximized(_hWnd)) {
|
: (localMouse.y <= (tbh + bh)))
|
||||||
|
&& isResizePermitted && !_data.disableTitleBar;
|
||||||
|
const bool isTop = (localMouse.y <= bh) && isResizePermitted;
|
||||||
|
if (shouldHaveWindowFrame()) {
|
||||||
|
// This will handle the left, right and bottom parts of the frame
|
||||||
|
// because we didn't change them.
|
||||||
|
const LRESULT originalRet = WNEF_EXECUTE_WINAPI_RETURN(DefWindowProcW,
|
||||||
|
0,
|
||||||
|
msg->hwnd,
|
||||||
|
WM_NCHITTEST,
|
||||||
|
msg->wParam,
|
||||||
|
msg->lParam);
|
||||||
|
if (originalRet != HTCLIENT) {
|
||||||
|
*result = originalRet;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// At this point, we know that the cursor is inside the client area
|
||||||
|
// so it has to be either the little border at the top of our custom
|
||||||
|
// title bar or the drag bar. Apparently, it must be the drag bar or
|
||||||
|
// the little border at the top which the user can use to move or
|
||||||
|
// resize the window.
|
||||||
|
if (!IsMaximized(msg->hwnd) && isTop) {
|
||||||
|
*result = HTTOP;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isTitleBar) {
|
||||||
|
*result = HTCAPTION;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
*result = HTCLIENT;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
const auto getHTResult =
|
||||||
|
[isTitleBar, localMouse, bh, isTop](const HWND _hWnd,
|
||||||
|
const WINDOWDATA &_data) -> LRESULT {
|
||||||
|
RECT clientRect = {0, 0, 0, 0};
|
||||||
|
WNEF_EXECUTE_WINAPI(GetClientRect, _hWnd, &clientRect)
|
||||||
|
const LONG ww = clientRect.right;
|
||||||
|
const LONG wh = clientRect.bottom;
|
||||||
|
const LONG bw = getSystemMetric(_hWnd, SystemMetric::BorderWidth, true);
|
||||||
|
if (IsMaximized(_hWnd)) {
|
||||||
|
if (isTitleBar) {
|
||||||
|
return HTCAPTION;
|
||||||
|
}
|
||||||
|
return HTCLIENT;
|
||||||
|
}
|
||||||
|
const bool isBottom = (localMouse.y >= (wh - bh));
|
||||||
|
// Make the border a little wider to let the user easy to resize
|
||||||
|
// on corners.
|
||||||
|
const int factor = (isTop || isBottom) ? 2 : 1;
|
||||||
|
const bool isLeft = (localMouse.x <= (bw * factor));
|
||||||
|
const bool isRight = (localMouse.x >= (ww - (bw * factor)));
|
||||||
|
const bool fixedSize = _data.fixedSize;
|
||||||
|
const auto getBorderValue = [fixedSize](int value) -> int {
|
||||||
|
// HTBORDER: non-resizeable window border.
|
||||||
|
return fixedSize ? HTBORDER : value;
|
||||||
|
};
|
||||||
|
if (isTop) {
|
||||||
|
if (isLeft) {
|
||||||
|
return getBorderValue(HTTOPLEFT);
|
||||||
|
}
|
||||||
|
if (isRight) {
|
||||||
|
return getBorderValue(HTTOPRIGHT);
|
||||||
|
}
|
||||||
|
return getBorderValue(HTTOP);
|
||||||
|
}
|
||||||
|
if (isBottom) {
|
||||||
|
if (isLeft) {
|
||||||
|
return getBorderValue(HTBOTTOMLEFT);
|
||||||
|
}
|
||||||
|
if (isRight) {
|
||||||
|
return getBorderValue(HTBOTTOMRIGHT);
|
||||||
|
}
|
||||||
|
return getBorderValue(HTBOTTOM);
|
||||||
|
}
|
||||||
|
if (isLeft) {
|
||||||
|
return getBorderValue(HTLEFT);
|
||||||
|
}
|
||||||
|
if (isRight) {
|
||||||
|
return getBorderValue(HTRIGHT);
|
||||||
|
}
|
||||||
if (isTitleBar) {
|
if (isTitleBar) {
|
||||||
return HTCAPTION;
|
return HTCAPTION;
|
||||||
}
|
}
|
||||||
return HTCLIENT;
|
return HTCLIENT;
|
||||||
}
|
|
||||||
const bool isTop = (mouse.y <= bh) && isResizePermitted;
|
|
||||||
const bool isBottom = (mouse.y >= (wh - bh));
|
|
||||||
// Make the border a little wider to let the user easy to resize
|
|
||||||
// on corners.
|
|
||||||
const int factor = (isTop || isBottom) ? 2 : 1;
|
|
||||||
const bool isLeft = (mouse.x <= (bw * factor));
|
|
||||||
const bool isRight = (mouse.x >= (ww - (bw * factor)));
|
|
||||||
const bool fixedSize = _data.fixedSize;
|
|
||||||
const auto getBorderValue = [fixedSize](int value) -> int {
|
|
||||||
// HTBORDER: non-resizeable window border.
|
|
||||||
return fixedSize ? HTBORDER : value;
|
|
||||||
};
|
};
|
||||||
if (isTop) {
|
*result = getHTResult(msg->hwnd, _data);
|
||||||
if (isLeft) {
|
return true;
|
||||||
return getBorderValue(HTTOPLEFT);
|
}
|
||||||
}
|
|
||||||
if (isRight) {
|
|
||||||
return getBorderValue(HTTOPRIGHT);
|
|
||||||
}
|
|
||||||
return getBorderValue(HTTOP);
|
|
||||||
}
|
|
||||||
if (isBottom) {
|
|
||||||
if (isLeft) {
|
|
||||||
return getBorderValue(HTBOTTOMLEFT);
|
|
||||||
}
|
|
||||||
if (isRight) {
|
|
||||||
return getBorderValue(HTBOTTOMRIGHT);
|
|
||||||
}
|
|
||||||
return getBorderValue(HTBOTTOM);
|
|
||||||
}
|
|
||||||
if (isLeft) {
|
|
||||||
return getBorderValue(HTLEFT);
|
|
||||||
}
|
|
||||||
if (isRight) {
|
|
||||||
return getBorderValue(HTRIGHT);
|
|
||||||
}
|
|
||||||
if (isTitleBar) {
|
|
||||||
return HTCAPTION;
|
|
||||||
}
|
|
||||||
return HTCLIENT;
|
|
||||||
};
|
|
||||||
*result = getHTResult(msg->hwnd, msg->lParam, data->windowData);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
case WM_GETMINMAXINFO: {
|
case WM_GETMINMAXINFO: {
|
||||||
// We can set the maximum and minimum size of the window in this
|
// We can set the maximum and minimum size of the window in this
|
||||||
|
@ -1573,7 +1658,7 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
|
||||||
const RECT rcWorkArea = monitorInfo.rcWork;
|
const RECT rcWorkArea = monitorInfo.rcWork;
|
||||||
const RECT rcMonitorArea = monitorInfo.rcMonitor;
|
const RECT rcMonitorArea = monitorInfo.rcMonitor;
|
||||||
const auto mmi = reinterpret_cast<LPMINMAXINFO>(msg->lParam);
|
const auto mmi = reinterpret_cast<LPMINMAXINFO>(msg->lParam);
|
||||||
if (isWin8OrGreator()) {
|
if (isWin8OrGreater()) {
|
||||||
// Works fine on Windows 8/8.1/10
|
// Works fine on Windows 8/8.1/10
|
||||||
mmi->ptMaxPosition.x = qAbs(rcWorkArea.left - rcMonitorArea.left);
|
mmi->ptMaxPosition.x = qAbs(rcWorkArea.left - rcMonitorArea.left);
|
||||||
mmi->ptMaxPosition.y = qAbs(rcWorkArea.top - rcMonitorArea.top);
|
mmi->ptMaxPosition.y = qAbs(rcWorkArea.top - rcMonitorArea.top);
|
||||||
|
|
Loading…
Reference in New Issue