refactor(FramelessWindow): use Qt API for window resizing (#81)
No longer use Win32API for window resizing. This should work under all platforms that support window resizing.
This commit is contained in:
parent
6f28878837
commit
6fc9534184
|
@ -1,33 +1,33 @@
|
||||||
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com>
|
||||||
|
// SPDX-FileCopyrightText: 2023 Tad Young <yyc12321@outlook.com>
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "framelesswindow.h"
|
#include "framelesswindow.h"
|
||||||
|
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QHoverEvent>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
#include <QWindow>
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif // _WIN32
|
|
||||||
|
|
||||||
FramelessWindow::FramelessWindow(QWidget *parent)
|
FramelessWindow::FramelessWindow(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, m_centralLayout(new QVBoxLayout(this))
|
, m_centralLayout(new QVBoxLayout(this))
|
||||||
|
, m_oldCursorShape(Qt::ArrowCursor)
|
||||||
|
, m_oldEdges()
|
||||||
{
|
{
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
// The Qt::WindowMinMaxButtonsHint or Qt::WindowMinimizeButtonHint here is to
|
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
|
||||||
// provide the ability to use Winkey + Up/Down to toggle minimize/maximize.
|
|
||||||
// But a bug introduced in Qt6 that this flag will break the WM_NCHITTEST event.
|
|
||||||
// See: QTBUG-112356 and discussion in https://github.com/BLumia/pineapple-pictures/pull/81
|
|
||||||
// Thanks @yyc12345 for finding out the source of the issue.
|
|
||||||
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
|
|
||||||
#else
|
#else
|
||||||
// There is a bug in Qt 5 that will make pressing Meta+Up cause the app
|
// There is a bug in Qt 5 that will make pressing Meta+Up cause the app
|
||||||
// fullscreen under Windows, see QTBUG-91226 to learn more.
|
// fullscreen under Windows, see QTBUG-91226 to learn more.
|
||||||
// The bug seems no longer exists in Qt 6 (I only tested it under Qt 6.3.0).
|
// The bug seems no longer exists in Qt 6 (I only tested it under Qt 6.3.0).
|
||||||
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
|
this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);
|
||||||
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
this->setMouseTracking(true);
|
||||||
|
this->setAttribute(Qt::WA_Hover, true);
|
||||||
|
this->installEventFilter(this);
|
||||||
|
|
||||||
m_centralLayout->setContentsMargins(QMargins());
|
m_centralLayout->setContentsMargins(QMargins());
|
||||||
}
|
}
|
||||||
|
@ -43,89 +43,103 @@ void FramelessWindow::setCentralWidget(QWidget *widget)
|
||||||
m_centralWidget = widget;
|
m_centralWidget = widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FramelessWindow::nativeEvent(const QByteArray &eventType, void *message, NATIVE_RESULT *result)
|
void FramelessWindow::installResizeCapture(QObject* widget)
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
widget->installEventFilter(this);
|
||||||
// https://stackoverflow.com/questions/43505580/qt-windows-resizable-frameless-window
|
}
|
||||||
// Too lazy to do this now.. just stackoverflow it and did a copy and paste..
|
|
||||||
Q_UNUSED(eventType)
|
bool FramelessWindow::eventFilter(QObject* o, QEvent* e)
|
||||||
MSG* msg = static_cast<MSG*>(message);
|
{
|
||||||
|
switch (e->type()) {
|
||||||
|
case QEvent::HoverMove:
|
||||||
|
{
|
||||||
|
QWidget* wg = qobject_cast<QWidget*>(o);
|
||||||
|
if (wg != nullptr)
|
||||||
|
return mouseHover(static_cast<QHoverEvent*>(e), wg);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QEvent::MouseButtonPress:
|
||||||
|
return mousePress(static_cast<QMouseEvent*>(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
return QWidget::eventFilter(o, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FramelessWindow::mouseHover(QHoverEvent* event, QWidget* wg)
|
||||||
|
{
|
||||||
|
QWindow* win = window()->windowHandle();
|
||||||
|
Qt::Edges edges = this->getEdgesByPos(wg->mapToGlobal(event->oldPos()), win->frameGeometry());
|
||||||
|
|
||||||
|
// backup & restore cursor shape
|
||||||
|
if (edges && !m_oldEdges)
|
||||||
|
// entering the edge. backup cursor shape
|
||||||
|
m_oldCursorShape = win->cursor().shape();
|
||||||
|
if (!edges && m_oldEdges)
|
||||||
|
// leaving the edge. restore cursor shape
|
||||||
|
win->setCursor(m_oldCursorShape);
|
||||||
|
|
||||||
|
// save the latest edges status
|
||||||
|
m_oldEdges = edges;
|
||||||
|
|
||||||
|
// show resize cursor shape if cursor is within border
|
||||||
|
if (edges) {
|
||||||
|
win->setCursor(this->getCursorByEdge(edges, Qt::ArrowCursor));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg->message == WM_NCHITTEST) {
|
|
||||||
if (isMaximized()) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*result = 0;
|
bool FramelessWindow::mousePress(QMouseEvent* event)
|
||||||
const LONG borderWidth = 8;
|
|
||||||
RECT winrect;
|
|
||||||
GetWindowRect(reinterpret_cast<HWND>(winId()), &winrect);
|
|
||||||
|
|
||||||
// must be short to correctly work with multiple monitors (negative coordinates)
|
|
||||||
short x = msg->lParam & 0x0000FFFF;
|
|
||||||
short y = (msg->lParam & 0xFFFF0000) >> 16;
|
|
||||||
|
|
||||||
bool resizeWidth = minimumWidth() != maximumWidth();
|
|
||||||
bool resizeHeight = minimumHeight() != maximumHeight();
|
|
||||||
if (resizeWidth) {
|
|
||||||
//left border
|
|
||||||
if (x >= winrect.left && x < winrect.left + borderWidth) {
|
|
||||||
*result = HTLEFT;
|
|
||||||
}
|
|
||||||
//right border
|
|
||||||
if (x < winrect.right && x >= winrect.right - borderWidth) {
|
|
||||||
*result = HTRIGHT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (resizeHeight) {
|
|
||||||
//bottom border
|
|
||||||
if (y < winrect.bottom && y >= winrect.bottom - borderWidth) {
|
|
||||||
*result = HTBOTTOM;
|
|
||||||
}
|
|
||||||
//top border
|
|
||||||
if (y >= winrect.top && y < winrect.top + borderWidth) {
|
|
||||||
*result = HTTOP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (resizeWidth && resizeHeight) {
|
|
||||||
//bottom left corner
|
|
||||||
if (x >= winrect.left && x < winrect.left + borderWidth &&
|
|
||||||
y < winrect.bottom && y >= winrect.bottom - borderWidth)
|
|
||||||
{
|
{
|
||||||
*result = HTBOTTOMLEFT;
|
if (event->buttons() & Qt::LeftButton && !isMaximized()) {
|
||||||
}
|
QWindow* win = window()->windowHandle();
|
||||||
//bottom right corner
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
if (x < winrect.right && x >= winrect.right - borderWidth &&
|
Qt::Edges edges = this->getEdgesByPos(event->globalPosition().toPoint(), win->frameGeometry());
|
||||||
y < winrect.bottom && y >= winrect.bottom - borderWidth)
|
|
||||||
{
|
|
||||||
*result = HTBOTTOMRIGHT;
|
|
||||||
}
|
|
||||||
//top left corner
|
|
||||||
if (x >= winrect.left && x < winrect.left + borderWidth &&
|
|
||||||
y >= winrect.top && y < winrect.top + borderWidth)
|
|
||||||
{
|
|
||||||
*result = HTTOPLEFT;
|
|
||||||
}
|
|
||||||
//top right corner
|
|
||||||
if (x < winrect.right && x >= winrect.right - borderWidth &&
|
|
||||||
y >= winrect.top && y < winrect.top + borderWidth)
|
|
||||||
{
|
|
||||||
*result = HTTOPRIGHT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*result != 0)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
QWidget *action = QApplication::widgetAt(QCursor::pos());
|
|
||||||
if (action == this) {
|
|
||||||
*result = HTCAPTION;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
#else
|
#else
|
||||||
return QWidget::nativeEvent(eventType, message, result);
|
Qt::Edges edges = this->getEdgesByPos(event->globalPos(), win->frameGeometry());
|
||||||
#endif // _WIN32
|
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||||
|
if (edges) {
|
||||||
|
win->startSystemResize(edges);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::CursorShape FramelessWindow::getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor)
|
||||||
|
{
|
||||||
|
if ((edges == (Qt::TopEdge | Qt::LeftEdge)) || (edges == (Qt::RightEdge | Qt::BottomEdge)))
|
||||||
|
return Qt::SizeFDiagCursor;
|
||||||
|
else if ((edges == (Qt::TopEdge | Qt::RightEdge)) || (edges == (Qt::LeftEdge | Qt::BottomEdge)))
|
||||||
|
return Qt::SizeBDiagCursor;
|
||||||
|
else if (edges & (Qt::TopEdge | Qt::BottomEdge))
|
||||||
|
return Qt::SizeVerCursor;
|
||||||
|
else if (edges & (Qt::LeftEdge | Qt::RightEdge))
|
||||||
|
return Qt::SizeHorCursor;
|
||||||
|
else
|
||||||
|
return default_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Qt::Edges FramelessWindow::getEdgesByPos(const QPoint gpos, const QRect& winrect)
|
||||||
|
{
|
||||||
|
const int borderWidth = 8;
|
||||||
|
Qt::Edges edges;
|
||||||
|
|
||||||
|
int x = gpos.x() - winrect.x();
|
||||||
|
int y = gpos.y() - winrect.y();
|
||||||
|
|
||||||
|
if (x < borderWidth)
|
||||||
|
edges |= Qt::LeftEdge;
|
||||||
|
if (x > (winrect.width() - borderWidth))
|
||||||
|
edges |= Qt::RightEdge;
|
||||||
|
if (y < borderWidth)
|
||||||
|
edges |= Qt::TopEdge;
|
||||||
|
if (y > (winrect.height() - borderWidth))
|
||||||
|
edges |= Qt::BottomEdge;
|
||||||
|
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,6 @@
|
||||||
|
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
typedef qintptr NATIVE_RESULT;
|
|
||||||
#else
|
|
||||||
typedef long NATIVE_RESULT;
|
|
||||||
#endif // QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
class QVBoxLayout;
|
class QVBoxLayout;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -24,11 +18,20 @@ public:
|
||||||
explicit FramelessWindow(QWidget *parent = nullptr);
|
explicit FramelessWindow(QWidget *parent = nullptr);
|
||||||
|
|
||||||
void setCentralWidget(QWidget * widget);
|
void setCentralWidget(QWidget * widget);
|
||||||
|
void installResizeCapture(QObject* widget);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool nativeEvent(const QByteArray& eventType, void* message, NATIVE_RESULT* result) override;
|
bool eventFilter(QObject *o, QEvent *e) override;
|
||||||
|
bool mouseHover(QHoverEvent* event, QWidget* wg);
|
||||||
|
bool mousePress(QMouseEvent* event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Qt::Edges m_oldEdges;
|
||||||
|
Qt::CursorShape m_oldCursorShape;
|
||||||
|
|
||||||
|
Qt::CursorShape getCursorByEdge(const Qt::Edges& edges, Qt::CursorShape default_cursor);
|
||||||
|
Qt::Edges getEdgesByPos(const QPoint pos, const QRect& winrect);
|
||||||
|
|
||||||
QVBoxLayout * m_centralLayout = nullptr;
|
QVBoxLayout * m_centralLayout = nullptr;
|
||||||
QWidget * m_centralWidget = nullptr; // just a pointer, doesn't take the ownership.
|
QWidget * m_centralWidget = nullptr; // just a pointer, doesn't take the ownership.
|
||||||
};
|
};
|
||||||
|
|
|
@ -162,6 +162,13 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
QTimer::singleShot(0, this, [this](){
|
QTimer::singleShot(0, this, [this](){
|
||||||
m_am->setupShortcuts();
|
m_am->setupShortcuts();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// allow some mouse events can go through these widgets for resizing window.
|
||||||
|
installResizeCapture(m_closeButton);
|
||||||
|
installResizeCapture(m_graphicsView);
|
||||||
|
installResizeCapture(m_graphicsView->viewport());
|
||||||
|
installResizeCapture(m_gv);
|
||||||
|
installResizeCapture(m_gv->viewport());
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user