Add a new way to set ignore areas.

Fixes: #6

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2020-05-03 18:33:58 +08:00
parent 60526b7be1
commit 123c36c374
3 changed files with 141 additions and 91 deletions

View File

@ -34,10 +34,10 @@ int main(int argc, char *argv[]) {
WinNativeEventFilter::WINDOWDATA data; WinNativeEventFilter::WINDOWDATA data;
// The window can't be resized if fixedSize is set to TRUE. // The window can't be resized if fixedSize is set to TRUE.
data.fixedSize = FALSE; data.fixedSize = FALSE;
// All the following values should not be DPI-aware, // All the following values should not be DPI-aware, just use the
// just use the original numbers, assuming the window's DPI // original numbers, assuming the scale factor is 1.0, don't scale
// is 96, don't scale them yourself, this code will // them yourself, this code will do the scaling according to DPI
// do the scaling according to DPI internally and automatically. // internally and automatically.
// Maximum window size // Maximum window size
data.maximumSize = QSize(1280, 720); data.maximumSize = QSize(1280, 720);
// Minimum window size // Minimum window size
@ -45,13 +45,19 @@ data.minimumSize = QSize(800, 540);
// How to set ignore areas: // How to set ignore areas:
// The geometry of something you already know, in window coordinates // The geometry of something you already know, in window coordinates
data.ignoreAreas.append(QRect(100, 0, 30, 30)); data.ignoreAreas.append(QRect(100, 0, 30, 30));
// The geometry of a widget, in window coordinates // The geometry of a widget, in window coordinates.
// It won't update automatically when the geometry of that widget has
// changed, so if you want to add a widget, which is in a layout and
// it's geometry will possibly change, to the ignore list, try the
// next method (ignoreObjects) instead.
data.ignoreAreas.append(pushButton_close.geometry()); data.ignoreAreas.append(pushButton_close.geometry());
// The **POINTER** of a QWidget or QQuickItem
data.ignoreObjects.append(ui->pushButton_minimize);
// Pass data as the second parameter // Pass data as the second parameter
WinNativeEventFilter::addFramelessWindow(reinterpret_cast<HWND>(widget.winId()), &data); WinNativeEventFilter::addFramelessWindow(reinterpret_cast<HWND>(widget.winId()), &data);
// Or // Or
WinNativeEventFilter::setWindowData(reinterpret_cast<HWND>(widget.winId()), &data); WinNativeEventFilter::setWindowData(reinterpret_cast<HWND>(widget.winId()), &data);
// Or // Or modify the window data of a specific window directly:
const auto data = WinNativeEventFilter::windowData(reinterpret_cast<HWND>(widget.winId())); const auto data = WinNativeEventFilter::windowData(reinterpret_cast<HWND>(widget.winId()));
data.borderWidth = 5; data.borderWidth = 5;
data.borderHeight = 5; data.borderHeight = 5;

View File

@ -28,6 +28,12 @@
#include <QGuiApplication> #include <QGuiApplication>
#include <QLibrary> #include <QLibrary>
#include <QOperatingSystemVersion> #include <QOperatingSystemVersion>
#ifdef QT_QUICK_LIB
#include <QQuickItem>
#endif
#ifdef QT_WIDGETS_LIB
#include <QWidget>
#endif
#include <QtMath> #include <QtMath>
#ifdef IsMinimized #ifdef IsMinimized
@ -643,7 +649,7 @@ void UpdateFrameMarginsForWindow(HWND handle) {
// 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).
margins = {0, 0, 1, 0}; margins.cyTopHeight = 1;
} }
m_lpDwmExtendFrameIntoClientArea(handle, &margins); m_lpDwmExtendFrameIntoClientArea(handle, &margins);
} }
@ -836,26 +842,24 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
m_lpSetWindowLongPtrW(msg->hwnd, GWL_STYLE, m_lpSetWindowLongPtrW(msg->hwnd, GWL_STYLE,
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN |
WS_CLIPSIBLINGS); WS_CLIPSIBLINGS);
// The following two functions make our window become a layered if (data->windowData.layeredWindow) {
// window, which can bring us better performance and it can also // Turn our window into a layered window to get better
// help us get rid of the three system buttons (minimize, maximize // performance and hopefully, to get rid of some strange bugs at
// and close). But they also break the Arcylic effect (introduced in // the same time. But this will break the Arcylic effect
// Win10 1709), if you use the undocumented API // (introduced in Win10 1709), if you use the undocumented API
// SetWindowCompositionAttribute to enable it for this window, the // SetWindowCompositionAttribute to enable it for this window,
// whole window will become totally black. Don't know why currently. // the whole window will become totally black. Don't know why
m_lpSetWindowLongPtrW(msg->hwnd, GWL_EXSTYLE, // currently.
WS_EX_APPWINDOW | WS_EX_LAYERED); m_lpSetWindowLongPtrW(msg->hwnd, GWL_EXSTYLE,
m_lpSetLayeredWindowAttributes(msg->hwnd, RGB(255, 0, 255), 0, WS_EX_APPWINDOW | WS_EX_LAYERED);
LWA_COLORKEY); // A layered window can't be visible unless we call
// SetLayeredWindowAttributes or UpdateLayeredWindow once.
m_lpSetLayeredWindowAttributes(msg->hwnd, RGB(255, 0, 255), 0,
LWA_COLORKEY);
}
// Trigger a frame change event to let us enter the WM_NCCALCSIZE // Trigger a frame change event to let us enter the WM_NCCALCSIZE
// message to remove our title bar as early as possible. // message to remove our title bar as early as possible.
updateWindow(msg->hwnd, true, false); updateWindow(msg->hwnd, true, false);
// Make sure our window have it's frame shadow.
// The frame shadow is drawn by Desktop Window Manager (DWM), don't
// draw it yourself. The frame shadow will get lost if DWM
// composition is disabled, it's designed to be, don't force the
// window to draw a frame shadow in that case.
UpdateFrameMarginsForWindow(msg->hwnd);
} }
switch (msg->message) { switch (msg->message) {
case WM_NCCALCSIZE: { case WM_NCCALCSIZE: {
@ -928,39 +932,6 @@ 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 getClientAreaInsets = [](HWND _hWnd) -> RECT {
// 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
// borders, and the default frame will be fine.
if (IsMaximized(_hWnd) && !IsFullScreen(_hWnd)) {
// Windows automatically adds a standard width border to all
// sides when a window is maximized.
const RECT frame = GetFrameSizeForWindow(_hWnd);
int frameThickness_x = frame.left;
int frameThickness_y = frame.bottom;
// The following two lines are two seperate functions in
// Chromium, it uses them to judge whether the window
// should draw it's own frame or not. But here we will
// always draw our own frame because our window is totally
// frameless, so we can simply use constants here. I don't
// remove them completely because I don't want to forget
// what it's about to achieve.
const bool removeStandardFrame = true;
const bool hasFrame = !removeStandardFrame;
if (!hasFrame) {
frameThickness_x -= 1;
frameThickness_y -= 1;
}
RECT rect;
rect.top = frameThickness_y;
rect.bottom = frameThickness_y;
rect.left = frameThickness_x;
rect.right = frameThickness_x;
return rect;
}
return {0, 0, 0, 0};
};
const RECT insets = getClientAreaInsets(msg->hwnd);
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
@ -971,10 +942,26 @@ 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);
clientRect->top += insets.top; // We don't need this correction when we're fullscreen. We will
clientRect->bottom -= insets.bottom; // have the WS_POPUP size, so we don't have to worry about
clientRect->left += insets.left; // borders, and the default frame will be fine.
clientRect->right -= insets.right; if (IsMaximized(msg->hwnd) && !IsFullScreen(msg->hwnd)) {
// Windows automatically adds a standard width border to all
// sides when a window is maximized. We have to remove it
// otherwise the content of our window will be cut-off from
// the screen.
// The value of border width and border height should be
// identical in most cases, when the scale factor is 1.0, it
// should be eight pixels.
const int bw =
getSystemMetric(msg->hwnd, SystemMetric::BorderWidth);
const int bh =
getSystemMetric(msg->hwnd, SystemMetric::BorderHeight);
clientRect->top += bh;
clientRect->bottom -= bh;
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,
// so the user can still mouse-over the taskbar to reveal it. // so the user can still mouse-over the taskbar to reveal it.
@ -1174,19 +1161,56 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
} }
const auto getHTResult = [](HWND _hWnd, LPARAM _lParam, const auto getHTResult = [](HWND _hWnd, LPARAM _lParam,
const WINDOW *_data) -> LRESULT { const WINDOW *_data) -> LRESULT {
const auto isInSpecificAreas = [](int x, int y, const auto isInSpecificAreas = [](const int x, const int y,
const QVector<QRect> &areas, const QVector<QRect> &areas,
qreal dpr) -> bool { const qreal dpr) -> bool {
for (auto &&area : qAsConst(areas)) { if (!areas.isEmpty()) {
if (!area.isValid()) { for (auto &&area : qAsConst(areas)) {
continue; if (!area.isValid()) {
continue;
}
if (QRect(qRound(area.x() * dpr),
qRound(area.y() * dpr),
qRound(area.width() * dpr),
qRound(area.height() * dpr))
.contains(x, y)) {
return true;
}
} }
if (QRect(qRound(area.x() * dpr), }
qRound(area.y() * dpr), return false;
qRound(area.width() * dpr), };
qRound(area.height() * dpr)) const auto isInSpecificObjects =
.contains(x, y, true)) { [](const int x, const int y,
return true; const QVector<QPointer<QObject>> &objects,
const qreal dpr) -> bool {
if (!objects.isEmpty()) {
for (auto &&object : qAsConst(objects)) {
#ifdef QT_WIDGETS_LIB
const auto widget = qobject_cast<QWidget *>(object);
if (widget) {
if (QRect(qRound(widget->x() * dpr),
qRound(widget->y() * dpr),
qRound(widget->width() * dpr),
qRound(widget->height() * dpr))
.contains(x, y)) {
return true;
}
}
#endif
#ifdef QT_QUICK_LIB
const auto quickItem =
qobject_cast<QQuickItem *>(object);
if (quickItem) {
if (QRect(qRound(quickItem->x() * dpr),
qRound(quickItem->y() * dpr),
qRound(quickItem->width() * dpr),
qRound(quickItem->height() * dpr))
.contains(x, y)) {
return true;
}
}
#endif
} }
} }
return false; return false;
@ -1209,27 +1233,37 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
const LONG bh = frame.bottom; const LONG bh = frame.bottom;
const LONG tbh = frame.top; const LONG tbh = frame.top;
const qreal dpr = GetDevicePixelRatioForWindow(_hWnd); const qreal dpr = GetDevicePixelRatioForWindow(_hWnd);
const bool isTitlebar = (mouse.y < tbh) && const bool isInIgnoreAreas = isInSpecificAreas(
!isInSpecificAreas(mouse.x, mouse.y, mouse.x, mouse.y, _data->windowData.ignoreAreas, dpr);
_data->windowData.ignoreAreas, dpr) && const bool isInDraggableAreas =
(_data->windowData.draggableAreas.isEmpty() _data->windowData.draggableAreas.isEmpty()
? true ? true
: isInSpecificAreas(mouse.x, mouse.y, : isInSpecificAreas(mouse.x, mouse.y,
_data->windowData.draggableAreas, _data->windowData.draggableAreas, dpr);
dpr)); const bool isInIgnoreObjects = isInSpecificObjects(
mouse.x, mouse.y, _data->windowData.ignoreObjects, dpr);
const bool isInDraggableObjects =
_data->windowData.draggableObjects.isEmpty()
? true
: isInSpecificObjects(mouse.x, mouse.y,
_data->windowData.draggableObjects,
dpr);
const bool isTitlebar = (mouse.y <= tbh) && !isInIgnoreAreas &&
isInDraggableAreas && !isInIgnoreObjects &&
isInDraggableObjects;
if (IsMaximized(_hWnd)) { if (IsMaximized(_hWnd)) {
if (isTitlebar) { if (isTitlebar) {
return HTCAPTION; return HTCAPTION;
} }
return HTCLIENT; return HTCLIENT;
} }
const bool isTop = mouse.y < bh; const bool isTop = mouse.y <= bh;
const bool isBottom = mouse.y > (wh - bh); const bool isBottom = mouse.y >= (wh - bh);
// Make the border wider to let the user easy to resize on // Make the border a little wider to let the user easy to resize
// corners. // on corners.
const int factor = (isTop || isBottom) ? 2 : 1; const int factor = (isTop || isBottom) ? 2 : 1;
const bool isLeft = mouse.x < (bw * factor); const bool isLeft = mouse.x <= (bw * factor);
const bool isRight = mouse.x > (ww - (bw * factor)); const bool isRight = mouse.x >= (ww - (bw * factor));
const bool fixedSize = _data->windowData.fixedSize; const bool fixedSize = _data->windowData.fixedSize;
const auto getBorderValue = [fixedSize](int value) -> int { const auto getBorderValue = [fixedSize](int value) -> int {
// HTBORDER: non-resizeable window border. // HTBORDER: non-resizeable window border.
@ -1328,7 +1362,13 @@ bool WinNativeEventFilter::nativeEventFilter(const QByteArray &eventType,
*result = ret; *result = ret;
return true; return true;
} }
case WM_ACTIVATE:
case WM_DWMCOMPOSITIONCHANGED: case WM_DWMCOMPOSITIONCHANGED:
// DWM won't draw the frame shadow if the window doesn't have a
// frame. So extend the window frame a bit to make sure we still
// have the frame shadow. But don't worry, the extended window frame
// won't be seen by the user because it's covered by the client area
// as what we did in WM_NCCALCSIZE.
UpdateFrameMarginsForWindow(msg->hwnd); UpdateFrameMarginsForWindow(msg->hwnd);
break; break;
case WM_DPICHANGED: case WM_DPICHANGED:
@ -1501,11 +1541,13 @@ void WinNativeEventFilter::moveWindowToDesktopCenter(HWND handle) {
// I think we should use rcMonitor, the monitor's whole area, // I think we should use rcMonitor, the monitor's whole area,
// to calculate the new coordinates of our window, not rcWork. // to calculate the new coordinates of our window, not rcWork.
const LONG mw = const LONG mw =
monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left; qAbs(monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left);
const LONG mh = const LONG mh =
monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top; qAbs(monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
const LONG ww = windowInfo.rcWindow.right - windowInfo.rcWindow.left; const LONG ww =
const LONG wh = windowInfo.rcWindow.bottom - windowInfo.rcWindow.top; qAbs(windowInfo.rcWindow.right - windowInfo.rcWindow.left);
const LONG wh =
qAbs(windowInfo.rcWindow.bottom - windowInfo.rcWindow.top);
m_lpMoveWindow(handle, qRound((mw - ww) / 2.0), qRound((mh - wh) / 2.0), m_lpMoveWindow(handle, qRound((mw - ww) / 2.0), qRound((mh - wh) / 2.0),
ww, wh, TRUE); ww, wh, TRUE);
} }

View File

@ -29,6 +29,7 @@
#endif #endif
#include <QAbstractNativeEventFilter> #include <QAbstractNativeEventFilter>
#include <QPointer>
#include <QRect> #include <QRect>
#include <QVector> #include <QVector>
#include <qt_windows.h> #include <qt_windows.h>
@ -38,9 +39,10 @@ class WinNativeEventFilter : public QAbstractNativeEventFilter {
public: public:
using WINDOWDATA = struct _WINDOWDATA { using WINDOWDATA = struct _WINDOWDATA {
BOOL fixedSize = FALSE, mouseTransparent = FALSE; BOOL fixedSize = FALSE, mouseTransparent = FALSE, layeredWindow = TRUE;
int borderWidth = -1, borderHeight = -1, titlebarHeight = -1; int borderWidth = -1, borderHeight = -1, titlebarHeight = -1;
QVector<QRect> ignoreAreas, draggableAreas; QVector<QRect> ignoreAreas, draggableAreas;
QVector<QPointer<QObject>> ignoreObjects, draggableObjects;
QSize maximumSize = {-1, -1}, minimumSize = {-1, -1}; QSize maximumSize = {-1, -1}, minimumSize = {-1, -1};
}; };