Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2020-08-06 15:36:15 +08:00
parent db8c6b5cc5
commit 12a6f86850
3 changed files with 332 additions and 219 deletions

View File

@ -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();

View File

@ -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 {

View File

@ -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);